He began to practice [red] team of PHP-Audit-Labs code audit Day8
link: https://github.com/hongriSec/PHP-Audit-Labs
interested students can go to Exercise
Prior knowledge:
content title comes from PHP SECURITY 2017 CALENDAR
Day. 8 - Candle code is as follows:
header("Content-Type: text/plain");
function complexStrtolower($regex, $value) {
return preg_replace(
'/(' . $regex . ')/ei',
'strtolower("\\1")',
$value
);
}
foreach ($_GET as $regex => $value) {
echo complexStrtolower($regex, $value) . "\n";
}
Vulnerability Analysis:
This question is examined preg_replace
function uses /e
pattern, leading to code execution problem. We found that the above codes 第11行
at the GET
request came embodiment the parameters in complexStrtolower
the function, and the variable $regex
and $value
also used in the presence of the code execution mode preg_replace
function. So, we can control the preg_replace
function 第1个
, 第3个参数
to execute code.
preg_replace () function:
(PHP 4, PHP 5, PHP 7)
Features:
preg_replace function performs a regular expression search and replace.
definition:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]])
Search subject
matching pattern
portion to replacement
be replaced.
Description:
parameter | description |
---|---|
pattern | To search pattern, it can be a string or an array of strings |
replacement | A string or array of strings alternative |
subject | To search the target string or array of strings replaced. |
limit | Alternatively, the maximum number of times for each pattern for each subject alternative string. The default is -1 (unlimited) |
count | Alternatively, the number of the replacement executed. |
$pattern
Presence/e
mode modifier, allowing code execution/e
Pattern Modifiers, is**preg_replace() **
to$replacement
as aphp
code to execute
return value:
If subject
an array, preg_replace()
returns a list, it returns a string otherwise.
If a match is found checked, after the replacement subject
is returned, returned unchanged in other cases subject
. If an error occurs, the return NULL
.
example:
However, as the code can be executed 第2个参数
, but fixed 'strtolower("\\1")'
. On time, here it comes to knowledge of regular expressions back references, that is where the \ 1, we can refer W3Cschool explanation of:
Backward reference:
For a regular expression pattern or part of the pattern 两边添加圆括号
will cause the associated 匹配存储到一个临时缓冲区
, each sub-matching captured are stored in the order from left to right appear in regular expression pattern. Buffer number from the 1
start, you can store up to 99
capture the sub-expression. Each buffer can be used '\n'
to access, which n
is to identify a specific buffer of one or two decimal digits.
The title of the official to
payload :/?.*={${phpinfo()}}
not actually be used, because if the GET request parameter name illegal character, PHP will be replaced with an underscore, that.*
will become_*
. Here we provide a usablepayload :\S*=${phpinfo()}
, detailed analysis, please refer to the article: in-depth study and implementation of the code preg_replace
Case Analysis:
The case study, we selected a
CmsEasy 5.5
version.
Vulnerability POC site to provide security tools, procedures (methods) may carry offensive, only for safety research and teaching purposes at your own risk!
Vulnerability Analysis:
漏洞入口文件为/lib/tool/form.php
(84-94行),我们可以看到下面第7行处引用了preg_replace
:
function getform($name,$form,$field,$data) {
if (get('table') &&isset(setting::$var[get('table')][$name]))
$form[$name]=setting::$var[get('table')][$name];
if (get('form') &&isset(setting::$var[get('form')][$name]))
$form[$name]=setting::$var[get('form')][$name];
if (isset($form[$name]['default']))
$form[$name]['default']=preg_replace('/\{\?([^}]+)\}/e',"eval('return $1;')",$form[$name]['default']);
if (!isset($data[$name]) &&isset($form[$name]['default']))
$data[$name]=@$form[$name]['default'];
if (preg_match('/templat/',$name) &&empty($data[$name]))
$data[$name]=@$form[$name]['default'];
且使用了/e
模式。如果 $form[$name]['default']
的内容被正则匹配到,就会执行 eval
函数,导致代码执行。
我们再来看看这个getform()
函数在何处被引用。通过搜索,我们可以发现在 Cache/template/default/manage/add.html
程序中,调用了此函数。这里我们需要关注catid
(下面 第4行
代码),因为 catid
作为 $name
在preg_preolace()
函数中使用到,这是我们成功利用漏洞的关键。 add.html
中的关键代码如下:
<div class="hid_box">
<strong>{lang(addcategory)}</strong>
<div class="hbox" style="background:none;">
{form::getform('catid',$form,$field,$data)}
</div>
</div>
那么问题来了, catid
是在何处定义的,或者说与什么有关?通过搜索,我们发现 lib/table/archive.php
文件中的 get_form()
函数对其进行了定义。
如图所示,我们可以看到该函数 return
了一个数组,数组里包含了catid
、 typeid
等参数对应的内容。仔细查看,发现其中又嵌套着一个数组。在 第6行
处 发现了 default
字段,这个就是我们上面提到的$form[$name]['default']
。
而上图 第6行
的 get()
方法在lib/tool/front_class.php
中,它是程序内部封装的一个方法。可以看到根据用户的请求方式, get()
方法会调用 front
类相应的 get
方法或 post
方法,具体代码如下:
front
类的 get
方法和 post
方法如下,看到其分别对应静态数组:
继续跟进静态方法 get
和post
,可以看到在 front
类中定义的静态属性:
这就意味着前面说的 $form[$name]['default']
中 name
和 default
的内容,都是我们可以控制的。
我们屡一下思路,get_form
函数定义了catid
的值, catid
对应的 default
字段又存在代码执行漏洞。而catid
的值由 get('catid')
决定,这个 get('catid')
又是用户可以控制的。所以我们现在只要找到调用 get_form
函数的地方,即可触发该漏洞。通过搜索,我们发现在 /lib/default/manage_act.php
文件的第10行
调用了 get_form()
函数,通过 View
模板直接渲染到前台显示:
这就形成了这套程序整体的一个执行流程,如下图所示:
漏洞利用:
1、首先打开首页,点击游客投稿
2、进入到相应的页面,传给catid
值,让他匹配到 /\{\?([^}]+)\}/e
这一内容,正则匹配的内容也就是{?(任意内容)}
,所以我们可以构造payload: catid={?(phpinfo())}
修复建议:
漏洞是 preg_replace()
存在/e
模式修正符,如果正则匹配成功,会造成代码执行漏洞,因此为了避免这样的问题,我们避免使用 /e
模式修正符,如下图第7行:
结语
再次感谢【红日团队】