Basic SQL Injection Vulnerability Analysis in PHP
Basic Injection
SQL parameters are concatenated without any filtering.
<?php
$con = mysql_connect("localhost","root","root");
if (!$con){die('Could not connect: ' . mysql_error());}
mysql_select_db("test", $con);
$id = stripcslashes($_REQUEST[ 'id' ]);
$query = "SELECT * FROM users WHERE id = $id ";
$result = mysql_query($query)or die(''.mysql_error().'');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>
Test statement: id=1 UNION SELECT user(),2,3,4 from users
Wide Byte Injection
A. MySQL Wide Character Injection
Example code:
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$query = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die(''.mysql_error().'');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>
Test statement: %df%27
MySQL's characteristic is that gbk is a multi-byte encoding, two bytes represent a Chinese character, so %df and the following \ which is %5c becomes a Chinese character "運", which escapes the single quote.
According to the gbk encoding, the first byte's ASCII code is greater than 128, which is basical sufficient. For example, instead of using %df, we can use %a1.
The range of gb2312 encoding. Its high byte range is 0xA10xF7, the low byte range is 0xA10xFE, and \ is 0x5c, which is not in the low byte range.
Therefore, 0x5c is not part of the gb2312 encoding, so it does not cause wide byte injection. Expanding to all multi-byte encodings in the world, as long as the low byte range contains 0x5c, it can be used for wide character injection.
Fixing Wide Character Injection:
Option 1: Specify the character set for PHP connecting to MySQL
mysql_set_charset('gbk',$conn);
$id = mysql_real_escape_string($_GET['id']);
Option 2: Set character_set_client to binary (binary)
mysql_query("SET character_set_connection=gbk, character_set_results=gbk, character_set_client=binary",$conn);
Setting character_set_client to binary eliminates the issue of wide or multi-byte characters, and all data is transmitted in binary form, effectively preventing wide character injection.
B. PHP Encoding Conversion
Example code:
<?php
$con = mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test", $con);
mysql_query("SET character_set_connection=gbk,
character_set_results=gbk,character_set_client=binary", $con);
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$id=iconv('utf-8','gbk',$id);
$query = "SELECT * FROM users WHERE id ='{$id}' ";
$result = mysql_query($query)or die(''.mysql_error().'');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>
Test statement: 錦'
The character 錦 has a UTF-8 encoding of %e9%8c%a6, and a GBK encoding of %e5%5c.
After converting with iconv from UTF-8 to GBK, it becomes %e5%5c, and the subsequent ' is added by addslashes to become %5c%27, resulting in %e5%5c%5c%27, where two %5c are , which properly escape the backslash, allowing the ' to escape the single quote, causing an injection.
id=iconv('gbk','utf-8',id); // Test with %df%27
A GBK Chinese character is two bytes, while a UTF-8 Chinese charactre is three bytes. If we convert GBK to UTF-8, PHP will process two bytes at a time. Therefore, if the character before \ is odd, it will swallow the , allowing the ' to escape the restriction.
Other functions:
mb_convert_encoding(id,'utf-8','gbk')//GBKToUTF-8 is the same as iconv('gbk','utf-8',id)
Encoding and Decoding
Finding some encoding and decoding functions to bypass protection, such as urldecode(), rawurldecode(), base64_decode()
<?php
$con = mysql_connect("localhost","root","root");
mysql_select_db("test", $con);
$id = addslashes($_REQUEST['id']);
$id = urldecode($id);//$id = base64_decode($id);
$query = "SELECT * FROM users WHERE id = '{$id}'";
$result = mysql_query($query)or die(''.mysql_error().'');
while($row = mysql_fetch_array($result))
{
echo $row['0'] . " " . $row['1'];
echo "<br />";
}
echo "<br/>";
echo $query;
mysql_close($con);
?>
Test statement:
1'union select 1,2,3,4%23 Encode single quote as 1%2527union select 1,2,3,4%23
1'union select 1,2,3,4# Base64 encode MSd1bmlvbiBzZWxlY3QgMSwyLDMsNCM=
Second Injection
When stored, the escape characters disappear, making it 'hack', and when retrieved, it becomes 'hack', which can be used to close the single quote and cause injection.
Test:
CREATE TABLE test (user VARCHAR(20) NOT NULL);
INSERT INTO test values('hack'');
Example code:
// Test data create table test(
id INT NOT NULL,
user VARCHAR(100) NOT NULL,
pass VARCHAR(100) NOT NULL
)
INSERT INTO test values(1,'hack','hack');
// Test code
Global Protection Blind Spots
- The str_replace function filters single quotes, possibly leading to injection;
- The stripslashes() function deletes the backslashes added by addslashes(). Improper use of stripslashes() may lead to injection;
① Parameters like id=1, which are numeric, completely ignore GPC filtering; ② Key-value pairs where only the value is checked but not the key; ③ Sometimes global filtering only filters GET, POST, and COOKIE, but not SERVER.
② FILES injection, where global filtering only handles GET, POST, etc., but ignores FILES; ② Variable overwriting, dangerous functions: extract(), parse_str(), $$.
Vulnerability Protection
Basic idea: Input (to resolve numeric injection) - Escape processing (to resolve character injection) - Output (to resolve database error messages)
-
Check whether input data matches the expected format. PHP has many functions for checking input, from simple variable functions and character type functions (like is_numeric(), ctype_digit()) to complex Perl-compatible regular expression functions. If the program expects a number, consider using is_numeric() to check, or directly use settype() to convert its type, or use sprintf() to format it as a number.
-
Built-in PHP escape functions
Addslashes() http://php.net/manual/zh/function.addslashes.php magic_quotes_gpc http://php.net/manual/zh/info.configuration.php#ini.magic-quotes-gpc mysql_real_escape_string() http://php.net/manual/zh/function.mysql-real-escape-string.php mysql_escape_string() http://php.net/manual/zh/function.mysql-escape-string.php
- Preventing database error message leakage:
Set php.ini file display_errors = Off
Add a @ character before the database query function
The most effective way to prevent SQL injection attacks is to use prepared statements for database queries:
<?php
$mysqli = new MySQLi("localhost","root","root","test");
if(!$mysqli){
die($mysqli->error);
}
$sql = "select id,username,password from users where id=?";////Create a predefined object ? placeholder
$mysqli_stmt = $mysqli->prepare($sql);
$id=$_REQUEST['id'];
$mysqli_stmt->bind_param("i",$id);////Bind parameter
$mysqli_stmt->bind_result($id,$username,$password);////Bind result set
$mysqli_stmt->execute();//Execute
while($mysqli_stmt->fetch()){ //Fetch the bound result set
echo $id." " . $username . " " . $password;
}
echo "<br/>";
echo $sql;
$mysqli_stmt->free_result(); ////Close result set
$mysqli_stmt->close();
$mysqli->close();
?>