一、漏洞概述  

ThinkPHP是一个开源轻量级PHP框架,其6.0.13及以前版本存在一个文件包含漏洞。简要来讲,在多语言特性开启的条件下,远程攻击者可通过控制传入参数来实现任意PHP文件包含。默认条件下,该漏洞只能包含本地PHP文件;但在开启了register_argc_argv且安装了pcel/pear的环境下,攻击者可通过 get、header、cookie 等传入参数,实现目录穿越包含pearcmd 。最终,通过pearcmd实现写入webshell。该漏洞影响版本为:v6.0.1~v6.0.13、v5.0.x、v5.1.x。

二、漏洞分析  

本文的分析均使用6.0.12版本。该漏洞要成功被利用需要满足三个条件:

  • ThinkPHP开启了多语言特性

  • php.ini开启了register_argc_argv

  • 安装了pcel/pear

在app/middleware.php中开启多语言功能:

在Lang.php 193行处设置断点,发送如下报文:

GET /?lang=../../../../../public/index HTTP/1.1Host: localhost:80Cache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8Cookie: think_lang=zh-cnConnection: close

观察断点处的文件包含代码,file参数的值已经成功拼接了我们传入的../../../../../ public/index。

跟踪调用栈来查看漏洞的触发过程,由于系统开启了多语言功能,发送的请求将会进入/src/think/middleware/LoadLangPack.php的handle方法。

handle方法中调用detect方法,并将其返回的值赋值给$langset变量,然后判断langset是否为默认值,不是则带入到switchLangset方法。跟进detect方法,langSet初始为空,然后从get/header/cookie中判断是否存在对应的参数名,若有则赋值给langSet变量。get对应lang,header对应think-lang,cookie对应think_lang。

此处的93行有一个allow_lang_list,看似有对langSet的判断逻辑,但搜索allow_lang_list发现其默认为空。这意味着此处其实没有任何过滤,直接将获得的langSet值传递给range变量并返回到上层函数。

继续跟进switchlangset方法,其调用load。

Load方法里判断$name是否为文件,若是则调用parse方法,parse方法中最终漏洞触发点include $file。

显然,该漏洞只能实现php文件的包含。为了利用该漏洞,可以结合pearcmd的trick来实现RCE。

pecl是PHP中用于管理扩展而使用的命令行工具,而pear是pecl依赖的类库。在7.3及以前,pecl/pear是默认安装的;在7.4及以后,需要我们在编译PHP的时候指定--with-pear才会安装。但是,在Docker任意版本镜像中,pcel/pear都会被默认安装,安装的路径在/usr/local/lib/php。

Pear的pearcmd.php文件可以获取命令行参数并执行对应Commands,运行该文件可以看到它的功能。

查看pearcmd.php,argv从$_SERVER['argv']中获得。

PEAR_Command::setFrontendType('CLI');$all_commands = PEAR_Command::getCommands();
// remove this next part when we stop supporting that crap-ass PHP 4.2if (!isset($_SERVER['argv']) && !isset($argv) && !isset($HTTP_SERVER_VARS['argv'])) {    echo 'ERROR: either use the CLI php executable, ' .         'or set register_argc_argv=On in php.ini';    exit(1);}
$argv = Console_Getopt::readPHPArgv();// fix CGI sapi oddity - the -- in pear.bat/pear is not removedif (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') {    unset($argv[1]);    $argv = array_values($argv);}

当php.ini开启register_argc_argv=On,用户输入可以赋值给$_SERVER['argv']:

因此,攻击者可从web访问pearcmd的命令行功能,并传入命令行功能的参数。

三、漏洞复现  

发送如下报文即在根目录创建test.php文件,内容含传入的<?=phpinfo(); ?>。

访问test.php,可以看到已经成功执行phpinfo。

发送如下报文即下载远端phpinfo.php到根目录,虽提示phpinfo.php不是tgz file,但仍可被成功下载。

访问phpinfo.php。


四、漏洞修复  

漏洞修复在官方的commit

https://github.com/top-think/framework/commit/c4acb8b4001b98a0078eda25840d33e295a7f099

核心是增加了校验,即如传入参数不符合正则表达式,最终被重置为默认的zh-cn。