| 0x02:通过分析tp5的漏洞来了解thinkphp
前期准备
1.环境:
phpstudy+thinkphp5
.0
.5
+phpstorm+xdebug+win
url:http:
//127.0.0.1/tp5/public/index.php
2.tp5.0.5源码下载:
https://www.kancloud.cn/manual/thinkphp5/118006
github下载
Composer命令下载(公众号后台回复tp5.0.5也可获取源码)
3.基础架构:https://www.kancloud.cn/manual/thinkphp5/118010 如图
4.看到这里是不是很懵逼,不知道干嘛,别着急嘛,我们先把tp5.0.5的框架搭建起来,如图。
5.漏洞复现一下:
先分析tp框架的运行流程
1.找到入口文件:
首先了解一下框架目录的构造,根据thinkphp开发手册,public下面的index.php是入口文件,里面定义/../application/是web的路径位置,其中start.php文件是加载框架引导文件。
查看start.php,里面包含了base.php,作用是加载基础文件,继续跟踪base.php
2.定义常量:
base.php里面全是定义的常量,需要注意的是包含了Loader.php
并且调用了类的方法,Loader.php就不用看了,直接看调用的方法
3.自动加载类:
就是这个方法thinkLoader::register(); 这里就是使用spl_auto_register方法注册一个系统自动加载和注册命令空间定义。根据开发手册大概的意思就是每次出现新的类他会自动加载类。为什么要自动加载类了?
spl_autoload_register函数是实现自动加载未定义类功能的的重要方法.
所谓的自动加载意思就是:
我们的new一个类的时候必须先include或者require的类文件,
如果没有include或者require,则会报错。
那这样我们就必须在文件头部写上许多include或require文件,非常麻烦。
方法如下:
public static function register($autoload = '')
{
// 注册系统自动加载
spl_autoload_register($autoload ?: 'think\Loader::autoload', true, true);
// 注册命名空间定义
self::addNamespace([
'think' => LIB_PATH . 'think' . DS,
'behavior' => LIB_PATH . 'behavior' . DS,
'traits' => LIB_PATH . 'traits' . DS,
]);
// 加载类库映射文件
if (is_file(RUNTIME_PATH . 'classmap' . EXT)) {
self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT));
}
// Composer自动加载支持
if (is_dir(VENDOR_PATH . 'composer')) {
self::registerComposerLoader();
}
// 自动加载extend目录
self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS);
}
然后直接看thinkError::register();方法,先自动加载thinkError的类(因为这里没有这个类嘛),然后再去走他里面有四个函数,作用分别是:(调试一下就知道了)
error_reporting() 函数规定报告哪个错误。
set_error_handler() 函数设置用户自定义的错误处理函数。
set_exception_handler() 函数设置用户自定义的异常处理函数。
register_shutdown_function该函数是来注册⼀个会在PHP中⽌时执⾏的函数。
public static function register()
{
error_reporting(E_ALL);
set_error_handler([__CLASS__, 'appError']);
set_exception_handler([__CLASS__, 'appException']);
register_shutdown_function([__CLASS__, 'appShutdown']);
}
这里是convention.php的数组都传入进去,这里是判断range是否设置变量,没有设置就为空,然而是设置了为sys,接下来是看name是不是string,如果是的话就判断是否存在内容.,如果不存在就将其小写,然后写入到config[sys]数组中去,值为null。如果有点的话,就以分割,然后存储为二维数组。接着判断是否$name是否为数组,因为不是数组直接跳到了else,将range和name数组合并赋值。(这个方法主要是用于设置和获取配置信息。它可以接受一个或多个配置参数,并将它们存储在一个静态数组中,后面会引用)
然后我们倒回去start.php,刚刚base.php是加载了许多常量,然后自动加载类,然后错误和异常处理机制,然后加载配置文件,这里调试一下就知道了。
接着直接看run方法,一步一步分析,调试看一下。
直接跳转到autoload类了,加载app类,自动加载类嘛
然后是直接调用run方法,这里是传入的参数为空,第一行,先判断$request是不是空,为空的话就调用Request类中的instance方法,得到的结果赋值给$request。不出意外的话就是自动记载Request类了,这边就直接跳了,直接看instance方法。
这里是instance判断是不是空,看下面的值是空的
传入的参数是空,所以第一个循环就没进去了,然后判断filter是不是空,当然应该也是空,那么就调用配置文件的get方法来获取这个default_filter参数。那到底是不是获取配置文件的这个参数呢,我们来分析分析get方法就好了
传入的参数为default_filter,第二个参数为空,先将config类中的sys赋值给$range,前面分析了是sys,然后判断$name,也就是我们第一个参数,是不是空,并且config[sys]这个参数是不是存在,如果存在这个参数,并且$name 为空的话,就直接返回该参数。//要注意的是config这数组变量里面的sys键里面存放了所有的配置。(看到这里我也晕了,原来我也是小丑自己写的也不懂了)
接着是不是存在点,不存在的话就将$name小写,也就是 default_filter小写,还是default_filter。//如果$config"sys"这个存在就返回该变量,否则就返回空。//那如果$name是存在点的呢,就以点进行分割,然后小写,形成多维数组,然后再在里面找,之前创建config参数的时候也有这个参数,就是找对应的参数罢了,然后retrue出来。
可以看到和我们想的是一样的,就是从配置里面获取对应的参数.(好在调试了一下,看不懂也没有关系,自己调试一下就知道了这里只是对代码的一个分析)
如果赋值给filter,就是从输入流中获取值,然后给input参数,这里就是 获取post的值。
提交参数:
获取post的值
接着我们在倒回app的run的方法,他创建了一个$request变量,这是一个Request类,然后获取里面的参数input是post的值,然后调用initCommon函数
然后看这个initConmmon函数,先判断self::$init是不是为空,可以看到为false,不为空,然后调用init函数。那我们先暂停,看看init函数
init函数的值:
第一行传入的module为空,然后下面加载的初始化文件是先判断存不存在init.php文件夹,这里是不存在的,所以直接跳到了else里面加载配置文件,然后path获取绝对路径,调试里面有,然后接下来是config的load进行加载
我们看load函数,这里传入的参数是$file,通过debug可以看到传入的值为:
D:PHPSTUDYthinkphpthink
-5.0
.2
public
/../application/config.php
然后继续获取$range=sys,然后判断这个config.php文件是不是存在。分析他的拓展名,很明显是php,是php的话就调用本身的set方法。之前是分析过set方法的。这里就不分析了。(这个方法主要是用于设置和获取配置信息。它可以接受一个或多个配置参数,并将它们存储在一个静态数组中)
然后继续回到init方法,根据前面的分析,可以知道接着加载数据库的配置,。然后接下来这里是读取扩展配置文件,这里是加载queue,php的扩展配置文件。
下面就是一些然后加载应用状态配置。加载公共文件,加载当前模块语言包,这里咱们就不分析了没啥用
最后调用Config中的get方法。是当参数为空时,就返回config["sys"]这一整数组
最后重新看到initCommon,前面先init初始化了一下配置变量,返回的结果是config["sys"]。然后将配置变量里面的class_suffix赋值给suffix变量。这一整步就是初始化应用。下面是应用调试模式。
可以看到应用调试模式调用了Env类的方法,这里肯定又要加载Env就不解释了,而config::get大家都是是获取配置文件中app_debug,直接我们来看看Env这个类中的get方法是干嘛的。
看到$name是app_debug,然后$default是true,说明配置文件中的app_debug为true,然后第一行代码,先将.替换为_,那app_debug就不变了。strtoupper是将字符串大写,因为常量一般都是大写的,环境变量也是,这是一种规范,我们看到下面那张图,ENV_PREFIX是环境变量的前缀,getenv就是获取环境变量,那就是获取PHP_APP_DEBUG的值,不是false就return $result,否则就是return $defaule那个环境变量应该就是.env里面设置,我们可以看到.env文件设置优先级高。然后才是返回配置文件的。
可以看到获取app_debug的值赋值给self::$debug变量,然后判断为false就把报错关了,否则就申请一个比较大的buffer
来到注册应用命名空间
加载额外文件:
设置系统时区,直接分析分析一下监听app_init是啥吧。
直接跳转到listen里面的去
回到run方法。返回了配置信息,那个$config还是配置信息,然后判断BIND_MODULE是否定义,这里就不看了,直接看下面
然后是检查多语言机制,否则读取默认语言。接着加载语言包。
获取调度信息,如果dispatch为空的话,就调用routeCheck方法,第一个参数为Request类,第二个参数为配置信息
那我们接着分析一下他是如何进行路由检测的。
然后他里面有个路由检测的chek方法,跟进去看看干了什么。
然后里面调用了$request->method(),这个就是漏洞产生的第一步了
我们先看这个函数干了啥,图中有解释
$this->{$this->method}($_POST)是一个动态调用方法的语法。在这个语法中,$this->method是一个变量,它存储了一个方法名,$_POST是该方法的参数。通过使用花括号将变量括起来,可以将变量的值作为方法名来调用该方法。因此,这行代码的含义是:调用$this对象中存储的方法名为$this->method的方法,并将$_POST数组作为参数传递给该方法。
接下来就是重点了,既然我们可以随便调用这个类的方法的,我们就可以通过调用__contruct来覆盖Request类的属性
先看代码,这样filter就被赋值为system()了一些危险函数了这里我们用system()
protected
function
__construct
($options = [])
{
foreach
($options
as
$name => $item) {
if
(property_exists(
$this
, $name)) {
$this
->$name = $item;
//重点
}
}
if
(is_null(
$this
->filter)) {
$this
->filter = Config::get(
'default_filter'
);
}
// 保存 php://input
$this
->input = file_get_contents(
'php://input'
);
}
怎么赋值了?我们可以实际操作一下。刚刚不是分析了我们可以调用request里面的所有方法。里面传入的值是POST,我们可控,然后里面是有一个$this->$name=$item,就是把传入的值判断是不是自己得属性,然后赋值给他,也就是说,我们可以对任意的 自己的属性赋值,我们先看看返回什么。那么可以先构造_method=__construct&method=POST
然后当上面执行后会调用__construct方法,我们断点看看。我们发现他会已数组的形势传给construct的方法,然后进行覆盖Request类的属性,所以这里就可以覆盖filter为system,为什么要覆盖了,我们接着往下看
我们先倒回run方法;当经过$dispatch = self::routeCheck($request, $config)检查调用的路由,然后会根据debug开关来选择是否执行Request::instance()->param(),这里我们是开了debug的所以我们直接往下走
看param方法:里面返回的input很神奇有我们想要的东西
我们来到input方法,将被__contruct覆盖掉的filter字段值回调进filterValue()
所以再filterValue函数里面会call_user_func调用system造成rce。
梳理一下:$this->method可控导致可以调用__contruct()覆盖Request类的filter字段,然后App::run()执行判断是debug执行$request->param(),而$request->param()会进入到input()方法,在这个方法中将被覆盖的filter回调call_user_func(),造成rce。总结图就是这样子的,为什么讲这么多只不过是让大家更加理解thinkphp框架的构造
所以当我们用下面的exp是可以执行命令的
参考链接:
https:
//xz.aliyun.com/t/3845
https:
//www.shuzhiduo.com/A/D854Eop6dE/
https:
//blog.csdn.net/qq_62989306/article/details/126911801
| 0x03:最后
只是记录下分析tp链子,都是踩在巨人的肩膀上面一步一步分析,如果让我挖还真不一定能挖一条。复现只不过可以加深对漏洞的理解,从而不断的fuzz,希望自己也能挖一条。
最近去医院看了一下病,医生说我老是熬夜导致心脏不好,老是流鼻血,所以建一个早起早睡的安全群,毕竟身体是革命的本钱,其实是自己老是约束不了自己,所以有了以下想法。
1.我打算搞个奖励惩罚机制,比如把晚睡晚起的人罚钱,奖励给早睡早起的人
2.想31块为入群费用,因为根据一个月一天一块钱来的
3.这样子早起谁没有打卡就扣一块钱,然后一个月后把扣的钱分给坚持早睡早起的人
4.打算先试一个月试试,毕竟21天养成一个习惯
5.当你自律起来了,你觉得安全还会搞不起来嘛?
6.很多时候有些事情成功,就是需要持之以恒!
7.可以联系我!一起做一个早睡早起自律的人!!!!
12.00了睡觉了,兄弟们,早睡才能早起。
|
原文始发于微信公众号(湘安无事):全网最细的TP 5.X之变量覆盖导致rce
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论