环境准备
安装ThinkPHP 6.0
composer create-project topthink/think=6.0.x-dev v6.0
修改application/index/controller/Index.php Index类的代码
class Index{ public function index() { $payload = unserialize(base64_decode($_GET['payload'])); return 'ThinkPHP V6.x'; }}
开启ThinkPHP6调试
将根目录.example.env更改为.env,文件中添加:APP_DEBUG = true
POP链分析
__destruct()
依旧是全局搜索 __destruct() ,我们查看在 /vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php 中的__destruct

使 $this->autosave = false 可以触发 $this->save()
CacheStore
AbstractCache是一个抽象类,我们使用find usages寻找继承它的类

在 /vendor/topthink/framework/src/think/filesystem/CacheStore.php 中的 CacheStore 类继承了 AbstractCache 类,并实现了 save() 方法

save() 方法中涉及 getForStorage() 方法,我们跟进此方法
getForStorage()
回到 AbstractCache.php 中我们找到了 getForStorage() 方法,继续跟进 cleanContents()

cleanContents()
array_flip对数组反转,array_intersect_key取数组交集

然后函数会将 $contents 返回给 getForStorage() 中的 $cleaned ,经过 json_encode 后返回给前面的 save() 方法

$contents 变量接收函数返回值后,进入下面了逻辑,此时$this->store是可控的,我们可以调用任意类的set方法,如果这个指定的类不存在set方法,就有可能触发__call()。当然也有可能本身的set()方法就可以利用。
Notice:在对象中调用一个不可访问方法时,__call()会被调用。有关 __call() 方法的详细说明,参见php手册https://www.php.net/manual/zh/language.oop5.overloading.php#object.call
set()

我们利用在File类中的 set() 方法

serialize()方法
此处有两种利用方法,我们先分析利用 serialize() 方法的POP链

$this->options\['serialize'][0]可控,可以执行任意函数,参数为$data
我们从set()方法中可知,$data 来源于 $value 的传值,在继续从CacheStore 中可知 $value 来源于 $contents ,
即json_encode后的数据,由此我们需要使json_encode后的数据被当作代码执行。
此时需要注意一个问题