PHP代码审计之ZZCMS8.1

前言:

很早就想搞代码审计了,但是一直觉得自己其他方面还差很多,所以一直在各种补,虽说学的还没忘的快。这次选择ZZCMS8.1主要是当初,这个CMS自从离心小姐姐审计了一番后,然后我就看到吐司也有人审计,很多地方都开始审计,我就纳闷了,为啥会有这么多人审计,,,,离心给我说,估计是因为这是ZZCMS吧。。。

0X01

HTTP head SQL injection

用户登录处和管理员后台登录处都是用的同样的代码,所以都是可以注射的。

G:\zzcms8.1\user\logincheck.php 18行—23行

G:\zzcms8.1\admin\logincheck.php 19行—24行

1
2
3
4
$ip=getip();
$sql="select * from zzcms_login_times where ip='$ip' and count>='".trytimes."' and unix_timestamp()-unix_timestamp(sendtime)<".jgsj." ";
$rs = query($sql); //执行一条 MySQL 查询
$row= num_rows($rs); //返回结果集中行的数量

然后跟进一下 getip() 函数

G:\zzcms8.1\inc\function.php 72行—84行

1
2
3
4
5
6
7
8
9
10
11
12
13
function getip(){ 
if (getenv("HTTP_CLIENT_IP") && strcasecmp(getenv("HTTP_CLIENT_IP"), "unknown"))
$ip = getenv("HTTP_CLIENT_IP"); //getenv() 获取一个环境变量的值
else if (getenv("HTTP_X_FORWARDED_FOR") && strcasecmp(getenv("HTTP_X_FORWARDED_FOR"), "unknown")) //strcasecmp() 比较两个字符串(不区分大小写),如果一直返回0,否则根据情况返回正负相差的数
$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);
}

然后,我们可以看出,HTTP_CLIENT_IP 和 HTTP_X_FORWARDED_FOR 都是可以自己伪造的,并且没有任何限制,所以,这就是一个明显的注入点。

我们就验证下常用的 HTTP_X_FORWARDED_FOR ,来构造下注入语句,测试下。

1
X-Forwarded-For: 0.0.0.0' and sleep(10) and '1'='1

在用户登录处或者后台管理页面登录处,用 burp 抓包下,添加下上句的 payload ,然后就可以看出页面延时了10秒,从而证明存在注入。

0X02

Reflected XSS

在根目录的 uploadimg_form.php 文件里,存在两处没有任何过滤的可控输入输出。

G:\zzcms8.1\uploadimg_form.php 66行—67行

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

可以看出可控参数 noshuiyin 和 imgid 都没有任何过滤,所以我们的 payload 只需要闭合下标签就可以达到攻击效果。

1
2
uploadimg_form.php?noshuiyin="><script>alert(document.cookie)</script>
uploadimg_form.php?imgid="><script>alert(document.cookie)</script>

这个文件也没有权限控制跳转,所以可以直接弹出弹框。

0X03

Storage type XSS

G:\zzcms8.1\one\link.php 7行—21行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (isset($_REQUEST["action"])=="add"){
checkyzm($_POST["yzm"]);
session_write_close();
$sitename = isset($_POST['sitename'])?$_POST['sitename']:"";
$url = isset($_POST['url'])?addhttp($_POST['url']):"";
$logo = isset($_POST['logo'])?addhttp($_POST['logo']):"";
$content = isset($_POST['content'])?$_POST['content']:"";

if ($sitename==''||$url==''||$logo==''||$content==''){
showmsg('请完整填写您的信息');
}

query("insert into zzcms_link (sitename,url,logo,content,sendtime)values('$sitename','$url','$logo','$content','".date('Y-m-d H:i:s')."')");
showmsg('操作成功!提示:提交申请后,请做好本站链接——如果没有增加本站的链接,那么你的申请是不会被通过的。','link.php') ;
}

可以看出,这里只对 sitename,url,logo,content 这四个参数判断了一下是否存在和是否为空,然后就存到数据库里了,没有进行任何过滤和其它的验证。

G:\zzcms8.1\admin\linkmanage.php 127行—140行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$rsn=query("select bigclassname from zzcms_linkclass where bigclassid=".$row["bigclassid"]." ");
$rown=fetch_array($rsn); //从结果集中取得一行作为数字数组或关联数组
echo $rown["bigclassname"]?></a></td>
<td><b><?php echo $row["sitename"]?></b><br>
<a href="<?php echo $row["url"]?>" target="_blank"><?php echo $row["url"]?></a><br>
<?php if ($row["logo"]<>""){?>
<img src="<?php echo $row["logo"]?>" width="150" height="50">
<?php }else{
echo "未填写LOGO地址";
}
?> </td>
<td><?php echo $row["content"]?></td>
<td><?php echo $row["sendtime"]?></td>

在输出的界面代码中也没有任何过滤,所以,这就造成了一个储存型XSS漏洞。

好吧,出现了玄学问题,,,从 link.php 文件里存到数据库的那四个参数,其中 “ > < 都被实体编码了,好玄学,,,那总得出来个审计出来个储存把?

G:\zzcms8.1\admin\link_save.php 21行—43行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$FriendSiteName=trim($_REQUEST["sitename"]); 
$url=addhttp(trim($_REQUEST["url"]));
$logo=addhttp(trim($_REQUEST["logo"]));
$content=trim($_REQUEST["content"]);
//trim()移除字符串左侧的字符 默认("\0"-NULL ,"\t"-制表符,"\n"-换行,"\x0B"-垂直制表符,"\r"-回车," "-空格)
if (isset($_POST["passed"])){
$passed=$_POST["passed"];
}else{
$passed=0;
}

if (isset($_POST["elite"])){
$elite=$_POST["elite"];
}else{
$elite=0;
}

if ($_REQUEST["action"]=="add"){
query("INSERT INTO zzcms_link (bigclassid,sitename,url,logo,content,passed,elite,sendtime)VALUES('$classid','$FriendSiteName','$url','$logo','$content','$passed','$elite','".date('Y-m-d H:i:s')."')");
}elseif ($_REQUEST["action"]=="modify") {
$id=$_POST["id"];
query("update zzcms_link set bigclassid='$classid',sitename='$FriendSiteName',url='$url',logo='$logo',content='$content',passed='$passed',elite='$elite',sendtime='".date('Y-m-d H:i:s')."' where id='$id'");
}

这个是管理后台页面的添加友联,代码和上面的差不多,基本一样,那这里总不会出现玄学问题了吧?

我们从这里插入常见的 payload ,<script>alert(1)</script>

恩,成功的弹出来了。

0X04

Reload vulnerability

G:\zzcms8.1\install\index.php 11行/51行—90行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$step = isset($_POST['step']) ? $_POST['step'] : 1;
<?php
switch($step) {
case '1'://协议
include 'step_'.$step.'.php';
break;
case '2'://环境
$pass = true;
$PHP_VERSION = PHP_VERSION;
if(version_compare($PHP_VERSION, '4.3.0', '<')) {
$php_pass = $pass = false;
} else {
$php_pass = true;
}
$PHP_MYSQL = '';
if(extension_loaded('mysql')) {
$PHP_MYSQL = '支持';
$mysql_pass = true;
} else {
$PHP_MYSQL = '不支持';
$mysql_pass = $pass = false;
}
$PHP_GD = '';
if(function_exists('imagejpeg')) $PHP_GD .= 'jpg';
if(function_exists('imagegif')) $PHP_GD .= ' gif';
if(function_exists('imagepng')) $PHP_GD .= ' png';
if($PHP_GD) {
$gd_pass = true;
} else {
$gd_pass = false;
}
$PHP_URL = @get_cfg_var("allow_url_fopen");//是否支持远程URL,采集有用
$url_pass = $PHP_URL ? true : false;
include 'step_'.$step.'.php';
break;
case '3'://查目录属性
include 'step_'.$step.'.php';
break;
case '4'://建数据库
include 'step_'.$step.'.php';
break;

这个ZZCMS也是通过 install.lock 来判断是否已经安装了的,然后我们根据上面的代码可以看出 step 参数如果为空的话,就默认从 1 开始,然后我们跟进下 step_1.php 这个文件。

G:\zzcms8.1\install\step_1.php 1行—5行

1
2
3
4
5
<?php
if(file_exists("install.lock")){
echo "<div style='padding:30px;'>安装向导已运行安装过,如需重安装,请删除 /install/install.lock 文件</div>";
}else{
?>

这个文件里判断了当前目录下是否存在 install.lock 文件,如果存在就提示已经安装了,那继续看下 step_2.php 的内容。

G:\zzcms8.1\install\step_2.php 1行—3行

1
2
3
<?php
if(@$step==2){
?>

这里没有继续判断是否存在 install.lock 文件,那继续看下剩下的 step_3/4.php 里有没有判断条件。

G:\zzcms8.1\install\step_3.php 1行—5行

1
2
3
4
5
<?php
if(@$step==3){
$token = md5(uniqid(rand(), true)); //uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。
$_SESSION['token']= $token;
?>

G:\zzcms8.1\install\step_4.php 1行—9行

1
2
3
4
5
6
7
8
9
<?php
if(@$step==4){
if ($_POST['token'] != $_SESSION['token'] || $_POST['token']=='' ){
echo "非法提交".$_POST['token']."<br>".$_SESSION['token'];
exit();
//}else{
//unset($_SESSION['token']);
}
?>

step_3.php 这个文件里也没有判断是否存在 install.lock 这个文件,只不过在 session 里添加了一个随机 token ,然后 step_4.php 文件里也没有判断 install.lock ,只是验证了一下在 3 里的那个 token ,所以,3 到 4 这个步骤不能越过,但是,这是个重装漏洞是妥妥的了。

我们只需要进入 install\index.php 路劲文件下,POST 一下 2 ,然后按顺序走下去就重装了这个CMS。

0X05

这个CMS后台管理处,还有一堆储存型XSS。。。。


PHP代码审计之ZZCMS8.1
https://sh1yan.top/2017/11/03/Zzcms8.1-of-PHP-code-audit/
作者
shiyan
发布于
2017年11月3日
许可协议