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"
Line 4: | Line 4: | ||
== Example 1 == | == Example 1 == | ||
− | Here follows a tipical Login Forms to authenticate user. | + | 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. |
Line 23: | Line 23: | ||
if ($result) { | if ($result) { | ||
if ($row = mysql_fetch_row($result)) { | if ($row = mysql_fetch_row($result)) { | ||
− | + | $authenticatedUserName = $row[0]; | |
} | } | ||
} | } | ||
Line 34: | Line 34: | ||
if ($sUserName = sAuthenticateUser($_POST["username"], | if ($sUserName = sAuthenticateUser($_POST["username"], | ||
$_POST["password"])) { | $_POST["password"])) { | ||
+ | |||
/* successfull authentication code goes here */ | /* successfull authentication code goes here */ | ||
... | ... | ||
... | ... | ||
} else { | } else { | ||
+ | |||
/* unsuccessfull authentication code goes here */ | /* unsuccessfull authentication code goes here */ | ||
... | ... | ||
Line 69: | Line 71: | ||
?></nowiki> | ?></nowiki> | ||
+ | The above example has two vulnerability: | ||
+ | |||
+ | * '''Authentication Bypass''' | ||
+ | *: by exploiting a SQL Injection vulnerability Authentication you can authenticate as : | ||
+ | *:: <nowiki>username ' OR 1=1 #</nowiki> | ||
+ | *:: <nowiki>password </nowiki>''anything'' | ||
+ | * '''Information Disclosure''' | ||
+ | *: an attacker may retrieve ''db.inc'' on unproper configured WEB Server | ||
== Example 2 == | == Example 2 == | ||
Line 102: | 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. | ||
= Description = | = Description = | ||
Line 108: | Line 119: | ||
=== Escaping Quotes === | === 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 '''<nowiki>'''</nowiki>''' | ||
+ | |||
+ | ''magic_quotes_gpc'' should never be used since: | ||
+ | |||
+ | * only GET/POST/COOKIE data are escaped in served HTTP Request | ||
+ | * it doesn't guarantee compatibility between different DBMS | ||
+ | |||
+ | |||
+ | 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: | ||
+ | |||
+ | <nowiki> | ||
+ | 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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </nowiki> | ||
+ | |||
+ | |||
+ | and use a DBMS related function to escape quotes such as: | ||
+ | * MySQL: ''mysql_real_escape_string'' | ||
+ | * PostgreSQL: ''pg_escape_string'' | ||
+ | |||
+ | <nowiki> | ||
+ | function sEscapeString($sDatabase, $sQuery) { | ||
+ | $sResult=NULL; | ||
+ | |||
+ | switch ($sDatabase) { | ||
+ | case "mysql": | ||
+ | $sResult = mysql_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; | ||
+ | } | ||
+ | } | ||
+ | </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()''. | ||
+ | |||
+ | |||
+ | With properly quotes escaping we can prevent Authentication Bypass vulnerability in Example 1: | ||
+ | |||
+ | |||
+ | '''auth.php'''<nowiki> | ||
+ | |||
+ | <?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 */ | ||
+ | ... | ||
+ | ... | ||
+ | } | ||
+ | |||
+ | </nowiki> | ||
=== Prepared Statements === | === Prepared Statements === | ||
=== Data Validation === | === Data Validation === | ||
− | |||
− | |||
== PHP preventing LDAP Injection == | == PHP preventing LDAP Injection == | ||
=== Data Validation === | === Data Validation === | ||
+ | |||
+ | == Detecting Intrusions from WEBAPP == | ||
== Defeating Automated Tools == | == Defeating Automated Tools == | ||
= References = | = References = |
Revision as of 20:58, 29 May 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
- by exploiting a SQL Injection vulnerability Authentication you can authenticate as :
- 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 on his laptop your Database content, or even worst can spawn a remote shell into it.
Description
PHP preventing SQL Injection
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 '''
magic_quotes_gpc should never be used since:
- only GET/POST/COOKIE data are escaped in served HTTP Request
- it doesn't guarantee compatibility between different DBMS
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_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; } }
Both Oracle and Microsoft SQL Server connectors doesn't have a real escape_string function so you need to escape every ' with '' by using your own escapeing function or addslashes().
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 */ ... ... }