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 */
...
...
}