0x00 前言
最近学习php代码审计,也看了不少文章和视频,决定自己尝试动手分析,于是从网上找到了zzcms的源码,在本地搭建审计一波,写下该文章对审计的过程进行记录。第一次写文章,不足之处还请各位大佬多多指教。
0x01 环境准备
zzcms 2020(最后更新于2020-7-26)
phpstudy(php5.6.27+Apache+mysql)
Windows7 64位
源码下载地址:http://www.zzcms.net/about/6.htm
将源码放到phpstud的WWW目录后,根据使用手册配置好环境后访问/install进行安装,安装完成后访问首页如下:
0x02 代码审计
开始审计前,先大概看一下目录文件,了解一下网站结构,网站文件有点多,看目录名也不是都看得出是干嘛用的,好在使用手册中对cms的目录结构进行了说明,如下:
/install 安装程序目录(安装时必须有可写入权限)
/admin 默认后台管理目录(可任意改名)
/user 注册用户管理程序存放目录
/skin 用户网站模板存放目录;
/template 系统模板存放目录;
/inc 系统所用包含文件存放目录
/area 各地区显示文件
/zs 招商程序文件
/pp 品牌
/dl 代理
/zh 展会
/company 企业
/job 招聘
/zx 资讯
/special专题
/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文件存放目录
/js js文件存放目录
/html 静态页存放目录
/web.config 伪静态规则文件for iis7(万网比较常用)
/httpd.ini 伪静态规则文件for iss6
/.htaccess 伪静态规则文件for apache
nginx.conf
大概了解了网站的目录结构之后,使用seay代码审计工具的自动审计功能进行审计,配合审计结果进行手工审计。
重装漏洞
在开始审计时,我喜欢先访问insatll目录,看看是否存在重装漏洞
访问/install,出现如下界面
看看/install/index.php文件
通过switch函数判断$step的值,根据$step的值来执行安装步骤,而$step的值通过POST传输
step1.php
检查是否存在install.lock文件,存在时就会出现上述访问的界面。
那么当手动传入参数使step=2,就可跳过step1的文件检测,重装网站
前台反射XSS漏洞
uploadimg_form.php
这个文件中存在两处直接将GET方式提交的参数输出,构造XSS闭合语句
"><script>alert(1)</script>
imgid参数
noshuiyin参数
后台反射XSS漏洞
/admin/ad_manage.php
这里通过REQUEST请求获取keyword参数的值,并使用echo直接输出,那么这里通过GET和POST两种方式传入keyword的值,构造XSS语句,都可弹窗,但实际测试时出现一些不同
POST方式成功弹窗
GET方式弹窗失败,返回“无效参数”
看到这个结果,猜测应该是对GET提交的参数进行了拦截,忽略了POST提交的参数。于是全局搜索一下“无效参数”,跟踪到/inc/stopsqlin.php文件,发现如下代码
通过$_SERVER['REQUEST_URI']获取当前URI,并使用黑名单的方式检测URI中是否含有一些参数,并未检测POST数据,所以造成POST提交可弹窗的结果。
GET提交参数直接大写关键词绕过检测即可
后台SQL注入漏洞
/admin/ask.php 204行
$_COOKIE["askbigclassid"]参数未用引号包裹直接带入sql语句
而这段代码是add()函数的一部分,那么要执行这段语句,需要调用add()
搜索add(),找到add()函数被调用的地方
上述代码可知,当参数do的值为add时,即可实现add()函数的调用,而do又是用户可控的,那么参数do=add,同时cookie中添加askbigclassid参数
结果and语句被拦截,应该是存在检测机制,过滤了一些SQL关键词。全局搜索提示的语句,寻找网站的过滤代码,跟踪到/inc/stopsqlin.php
这里定义了一个stopsqlin()函数,使用黑名单的方式,对参数进行检测,当检测到config.php中定义的字符串,则报错
/inc/config.php中定义的关键词
但stopsqlin.php文件中,后面的这段代码存在缺陷,导致可绕过检测,造成注入
代码如下
这段代码首先使用$_SERVER获取当前URI,然后使用if语句对URI进行判断,当满足条件时才会调用stopsqlin()函数处理$_GET、$_POST、$_COOKIE和$REQUEST传递的参数
来看一下if语句
if (strpos($r_url,"siteconfig.php")==0 && strpos($r_url,"label")==0 && strpos($r_url,"template.php")==0)
这段语句表示当URI中不存在"siteconfig.php"、"label"和"template.php"这三个字符串时,才满足条件,所以只要我们在URI中提交参数包含这三个字符串中的任意一个,即可使该if语句为false,从而绕过检测。
最终数据包构造如下
成功执行SQL语句
存储XSS漏洞
漏洞的成因感觉比较有趣,本来这个cms是定义了一个zc_check()函数,使用addslashess、
htmlspecialchars对用户输入的参数进行了过滤,对SQL注入和XSS都起到了一定的防护作用。但同时又定义了另外一个函数stripfxg(),对参数做和zc_check()函数完全相反的操作,当两个函数同时调用时,就相当于没有做任何过滤。下面具体分析一下这两个函数。
/inc/stopsqlin.php=>zc_check()
6-16行,定义了zc_check()函数
7-13行,当传入参数不是数组时,判断是否开启gpc功能,(5.4之后默认是false),未开启则再使用trim()、htmlspecialchars()和addslashes()处理参数。
14-16行,当传入数组时,则遍历键值对,使用zc_check()函数对数组的每个参数进行处理
17-25行,使用zc_check()函数对$_COOKIE、$_GET、$POST传入的参数进行处理
所以当调用该函数时,addslashes()和htmlspecialchars()对参数进行了处理,是可以防止大部分SQL注入和XSS的。
/inc/function.php=>stripfgx()
571-580行,定义了stropfxg()函数
572行,stripslashes()删除了addslashes()函数添加的反斜杠
573-575行,当$htmlspecialchars_decode参数的值为true时,htmlspecialchars_decode()将html实体编码转换为普通的字符
这个函数恰好将zc_check()函数做的转义去掉,所以,只要调用stripfxg()函数,并将第二个参数设置为true的,就相当于没有防护,都可能会存在SQL注入和XSS。(不是很懂为啥要写这么个函数)
全局搜索stripfxg()
看到调用该函数的地方还挺多的
下面分别以前台和后台的存储XSS漏洞各一个为例
后台存储xss
/admin/siteconfig.php
文件第4行,包含admin.php文件=>admin.php第2行包含/inc/conn.php文件,conn.php中又同时包含了function.php和stopsqlin.php文件
第1136行,使用stripfxg()函数对sitecount参数进行了处理,最后被写入/inc/config.php文件,在/index.php输出
访问/admin/siteconfig.php,写入xss代码保存
访问首页
触发XSS代码
前台存储XSS
/zt/show.php
第256行,使用了stripfxg()函数处理$content参数,并拼接赋值给$gsjj变量
往后查找$gsjj函数,看是否输出了$gsjj变量
虽然没有直接输出$gsjj变量,但是374行中,将$strout中的"{#gsjj}"字符串替换为了$gsjj的值,然后在385行将$strout输出。那么,如果$content的值可控的话,就可以将$content的值构造为XSS语句,在385行进行输出,从而造成XSS漏洞。
往上查找$content变量,在show.php文件中未找到,那么应该是在include包含的文件中定义,于是一个个打开搜索$content变量
/zt/top.php
在49行找到$content变量的赋值,为数组$row["content"]的值
继续往上查找$row
第30行,使用fetch_array()查询数据库中的数据取一行赋值给数组$row
第17行,通过id查询zzcms_user表中的数据
在数据库中查询,发现content字段
随后寻找哪里能够插入或修改zzcms_user表中的content字段,想到上面的两个用户均为前台注册账户,那么前台用户注册或信息修改的地方是能插入content字段的值,于是在user目录下搜索"update zzcms_user",在/user/manage.php文件中可修改content字段的值。
/user/manage.php
第180行中,修改zzmcs_user表的数据,包含了content字段
继续看代码,上面的代码都是包含在if($action=="modify")的条件下的,而$action通过GET方式传入,可人为控制
接下来构造数据包看是否能修改content字段的值
访问/user/manage.php看看
点击修改抓包
数据包中没有看到content参数,在POST中添加content参数发包,看看是否会修改content的值
发包后在数据库中查询
可以看到成功的修改了content的值,那就可以将content值修改为XSS语句
发包后访问/zt/show.php,由于这里修改的用户id为1,GET提交id=1
触发XSS代码弹窗
0x03 总结
这次是个人第一次比较完整的对一个cms进行审计,发现的漏洞比较少,比较遗憾的是没有发现getshell的点,不确定是没有还是自己没发现。感觉这个cms应该还有不少漏洞的,由于个人水平有限,只能到此为止了。
content在函数去掉斜杠之后应该就又可以注入了