This site is the archived OWASP Foundation Wiki and is no longer accepting Account Requests.
To view the new OWASP Foundation website, please visit https://owasp.org

Difference between revisions of "OWASP Backend Security Project PHP Security Programming"

From OWASP
Jump to: navigation, search
Line 112: Line 112:
 
</nowiki>
 
</nowiki>
  
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup on his laptop your Database content, or even worst can spawn a remote shell into it.
+
The above example is vulnerable to ''Blind SQL Injection'' attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.
  
 
= Description =
 
= Description =
Line 118: Line 118:
 
== PHP preventing SQL Injection ==
 
== PHP preventing SQL Injection ==
  
=== Escaping Quotes ===
+
=== DBMS authentication credentials ===
 +
 
 +
When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with ''.inc'' extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:
 +
 
 +
* Deny remote access to ''.inc'' files
 +
<nowiki>
 +
    <Files ~ “\.inc$”>
 +
        Order allow,deny
 +
        Deny from all
 +
    </Files>
 +
</nowiki>
 +
 
 +
* Secure File Inclusion
 +
<nowiki>
 +
<?php
 +
 
 +
if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){
 +
    define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');
 +
    ....
 +
}
 +
</nowiki>
 +
 
 +
<nowiki>
 +
<?php
 +
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');
 +
include 'dbms_handler.php';
 +
..
 +
?>
 +
</nowiki>
 +
 
 +
 
 +
* php_value
 +
 
 +
'''/etc/apache2/sites-enabled/000-owasp''' <nowiki>
 +
 
 +
<VirtualHost *>
 +
    DocumentRoot /var/www/apache2/
 +
    php_value mysql.default_host “127.0.0.1”
 +
    php_value mysql.default_user “owaspuser”
 +
    php_value mysql.default_password “owasppassword”
 +
    ....
 +
    ....
 +
    ....
 +
</VirtualHost>
 +
</nowiki>
 +
 
 +
'''dbmshandler.php''' <nowiki>
  
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>'''
+
<?php
 +
    function iMySQLConnect() {
 +
        return mysql_connect();
  
''magic_quotes_gpc'' should never be used since:
+
    }
 +
?>
 +
</nowiki>
  
* only GET/POST/COOKIE data are escaped in served HTTP Request
+
=== Escaping Quotes ===
* it doesn't guarantee compatibility between different DBMS
 
  
 +
''magic_quotes_gpc'' escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with '''<nowiki>'''</nowiki>''' rather than '''<nowiki>\'</nowiki>'''
  
 
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:
 
Since every application should be portable across WEB Servers we want to rollaback from ''magic_quotes_gpc'' each time a ''php'' script is running on WEB Server:
Line 169: Line 219:
 
     switch ($sDatabase) {
 
     switch ($sDatabase) {
 
     case "mysql":
 
     case "mysql":
         $sResult = mysql_escape_string($sQuery);
+
         $sResult = mysql_real_escape_string($sQuery);
 
         break;
 
         break;
 
    
 
    
Line 190: Line 240:
 
</nowiki>
 
</nowiki>
  
Both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function so you need to escape every ''<nowiki>'</nowiki>'' with ''<nowiki>''</nowiki>'' by using your own escapeing function or ''addslashes()''.
+
Since both Oracle and Microsoft SQL Server connectors doesn't have a real ''escape_string'' function software developer can create his own escapeing functions or use ''addslasshes()''.
 
 
  
 
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:
 
With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:
Line 241: Line 290:
  
 
</nowiki>
 
</nowiki>
 
=== Include file handling ===
 
  
 
=== Prepared Statements ===
 
=== Prepared Statements ===
Line 249: Line 296:
  
 
== PHP preventing LDAP Injection ==
 
== PHP preventing LDAP Injection ==
 +
 +
=== LDAP Authentication Credentials ===
  
 
=== Data Validation ===
 
=== Data Validation ===
Line 257: Line 306:
  
 
= References =
 
= References =
 +
 +
* Ilia Alshanetsky : "architect's Guide to PHP Security" - http://dev.mysql.com/tech-resources/articles/guide-to-php-security-ch3.pdf
 +
* OWASP : "OWASP Guide Project" - http://www.owasp.org/index.php/OWASP_Guide_Project

Revision as of 21:21, 2 June 2008

Overview

Example 1

Here follows a tipical Login Forms to authenticate user. Credentials are retrieved on a backend Database by using connection parameters stored in a .inc file.


auth.php

<?php
include('./db.inc');

function sAuthenticateUser($username, $password){
  $authenticatedUserName="";
  if ($link = iMysqlConnect()) {

    $query  = "SELECT username FROM users";
    $query .=                " WHERE username = '".$username."'";
    $query .=                " AND   password = md5('".$password."')";
    $result = mysql_query($query);

    if ($result) {
      if ($row = mysql_fetch_row($result)) {
         $authenticatedUserName =  $row[0];
      }
    }
  }

  return $authenticatedUserName;

}

if ($sUserName = sAuthenticateUser($_POST["username"], 
				   $_POST["password"])) {

  /* successfull authentication code goes here */
   ...
   ...
 } else {

  /* unsuccessfull authentication code goes here */
   ...
   ...
 }

?>


db.inc

<?php

define('DB_HOST',     "localhost");
define('DB_USERNAME', "user");
define('DB_PASSWORD', "password");
define('DB_DATABASE', "owasp");


function iMysqlConnect(){
  $link = mysql_connect(DB_HOST,
  	  DB_USERNAME,
			DB_PASSWORD);

  if ($link && mysql_select_db(DB_DATABASE))
    return $link;
  return FALSE;
}

?>

The above example has two vulnerability:

  • Authentication Bypass
    by exploiting a SQL Injection vulnerability Authentication you can authenticate as :
    username ' OR 1=1 #
    password anything
  • Information Disclosure
    an attacker may retrieve db.inc on unproper configured WEB Server

Example 2

The following sample code cames from a online book catalog.

getbook.php 


function aGetBookEntry($id) {
  $aBookEntry = NULL;
  $link = iMysqlConnect();

  $query = "SELECT * FROM books WHERE id = $id";
  $result = mysql_query($query);

  if ($result) {
    if ($row = mysql_fetch_array($result)) {
      $aBookEntry = $row;
    }
  }
  return $aBookEntry;
}

....
$id = $_GET['id'];
$aBookEntry = aGetBookEntry($id);

/* Display retrieved book information */
...
...


The above example is vulnerable to Blind SQL Injection attack. An attacker exploiting this vulnerability may backup all Database, or interact with DBMS underlying Operating System.

Description

PHP preventing SQL Injection

DBMS authentication credentials

When working with a DBMS through an authenticated connection application developers should be very carefull on how, and subsequently where, store authentication credentials to query such a backend engine. A configuration file with .inc extension should be avoided if left world wide readable by a web server since it's content can be easy retrieved. Such a issue can be easly fixed by:

  • Deny remote access to .inc files
     <Files ~ “\.inc$”>
        Order allow,deny
        Deny from all
     </Files>

  • Secure File Inclusion
<?php

if (!defined('SECURITY_INCLUDE_TOKEN') || SECURITY_INCLUDE_TOKEN != 'WfY56#!5150'){
     define ('DBMS_CONNECTION_STRING','mysql://owaspuser:owasppassword@localhost:3306');
     ....
}

<?php
define('SECURITY_INCLUDE_TOKEN', 'WfY56#!5150');
include 'dbms_handler.php';
..
?>


  • php_value
/etc/apache2/sites-enabled/000-owasp 

<VirtualHost *>
     DocumentRoot /var/www/apache2/
     php_value mysql.default_host “127.0.0.1”
     php_value mysql.default_user “owaspuser”
     php_value mysql.default_password “owasppassword”
     ....
     ....
     ....
</VirtualHost>

dbmshandler.php 

<?php
    function iMySQLConnect() {
        return mysql_connect();

    }
?>

Escaping Quotes

magic_quotes_gpc escapes quotes from HTTP Request by examing both GET/POST data and Cookie value. The truth is that any other data in HTTP Request isn't escaped and an evil user may attempt to exploit a SQL Injection vulnerability on other HTTP Request data such as User-Agent value. Another drawback is that while it performs well on MySQL (as example) it doesn't works with Microsoft SQL Server where single quote should be escaped with ''' rather than \'

Since every application should be portable across WEB Servers we want to rollaback from magic_quotes_gpc each time a php script is running on WEB Server:

function magic_strip_slashes() {
    if (get_magic_quotes()) {

       // GET
       if (is_array($_GET)) {
           foreach ($_GET as $key => $value) {
               $_GET[$key] = stripslashes($value);
           }
       }

       // POST
       if (is_array($_POST)) {
           foreach ($_GET as $key => $value) {
               $_POST[$key] = stripslashes($value);
           }
       }

       // COOKIE
       if (is_array($_COOKIE)) {
           foreach ($_GET as $key => $value) {
               $_COOKIE[$key] = stripslashes($value);
           }
       }
    }
}


and use a DBMS related function to escape quotes such as:

  • MySQL: mysql_real_escape_string
  • PostgreSQL: pg_escape_string
function sEscapeString($sDatabase, $sQuery) {
    $sResult=NULL;
 
    switch ($sDatabase) {
    case "mysql":
        $sResult = mysql_real_escape_string($sQuery);
        break;
  
    case "postgresql":
        $sResult = pg_escape_string($sQuery);
        break;

    case "mssql":
        $sResult = str_replace("'", "''",$sQuery);
        break;

    case "oracle":
        $sResult = str_replace("'", "''",$sQuery);
        break;
    }

    return $sResult;
}
}

Since both Oracle and Microsoft SQL Server connectors doesn't have a real escape_string function software developer can create his own escapeing functions or use addslasshes().

With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1:


auth.php

<?php
include('./db.inc');

function sAuthenticateUser($username, $password){
  $authenticatedUserName="";
  if ($link = iMysqlConnect()) {

    $query  = "SELECT username FROM users";
    $query .=                " WHERE username = '".$username."'";
    $query .=                " AND   password = md5('".$password."')";

    /* escape quotes */
    $result = sEscapeString("mysql", $query);

    if ($result) {
      if ($row = mysql_fetch_row($result)) {
         $authenticatedUserName =  $row[0];
      }
    }
  }

  return $authenticatedUserName;
}

/* start by rollback magic_quotes_gpc action (if any) */

magic_strip_slashes();


if ($sUserName = sAuthenticateUser($_POST["username"], 
                                   $_POST["password"])) {

  /* successfull authentication code goes here */
   ...
   ...
 } else {

  /* unsuccessfull authentication code goes here */
   ...
   ...
 }


Prepared Statements

Data Validation

PHP preventing LDAP Injection

LDAP Authentication Credentials

Data Validation

Detecting Intrusions from WEBAPP

Defeating Automated Tools

References