环境准备
安装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
后的数据被当作代码执行。
此时需要注意一个问题