1.打开是一个反序列化的入口
<?php //backup in source.tar.gz namespace App\Http\Controllers; class IndexController extends Controller { public function index(\Illuminate\Http\Request $request){ $payload=$request->input("payload"); if(empty($payload)){ highlight_file(__FILE__); }else{ @unserialize($payload); } } }首先是使用namespace导入一个命名空间,后面是他的路径 App\Http\Controllers
定义一个名为IndexController的类,它是继承Controller类的
定义一个公共的index方法,里面是Laravel路由默认调用的动作,参数\Illuminate\Http\Request $request ,使用完整命名空间\Illuminate\Http\Request,明确指定 类型。$request是Laravel封装的HTTP请求对象,用于获取用户传入的数据,如POST/GET参数,触发了会获取请求中名为payload的参数值支持POST、GET、JSON body等多种方式传参,然后赋值给$payload,然后if判断$payload是否为空,如果为空就高亮显示当前文件,否的话对他进行反序列化的操作,还提示了源码的位置source.tar.gz,下载下来分析源码
2.分析源码
他这是个Laravel框架的源码
头一次审框架的反序列化,有点看不懂,就按照wp的步骤来做一些这题
第一步:
首先先找function __destruct()方法,因为在unserialize的时候会触发他,也是链子的启动部分
phpstorm中英文状态 ctrl+shift+f搜索 一下function __destruct()
在TagAwareAdapter.php中发现了一个可以调用方法的地方,追进看看
agAwareAdapter.php文件部分代码
public function commit() { return $this->invalidateTags([]); } public function __destruct() { $this->commit(); }首先destruct方法触发了会调用本类的commit方法,上面是commit方法,触发了会返回本类的invalidateTags方法,参数为一个数组 ,向上找,找invalidateTags方法
public function invalidateTags(array $tags) { $ok = true; $tagsByKey = []; $invalidatedTags = []; foreach ($tags as $tag) { CacheItem::validateKey($tag); $invalidatedTags[$tag] = 0; } if ($this->deferred) { $items = $this->deferred; foreach ($items as $key => $item) { if (!$this->pool->saveDeferred($item)) { unset($this->deferred[$key]); $ok = false; } }首先将$ok设置为true,然后设置$tagsByKey为空数组,还有一个$invalidatedTags为空数组,然后循环遍历将$tags的值 遍历到$tag中,然后调用CacheItem类中的validateKey方法,里面的参数为$tag,然后下面是一个$invalidatedTags参数,他的索引值为$tag,赋值为0
if语句这个是关键代码,他首先判断本类的deferred属性是否存在,如果存在,就将他的值赋值给$items,然后使用循环遍历,给$items转换成键值对的形式,又是一个if语句,如果$this->pool对象没有saveDeferred方法,就unset销毁本类的 deferred属性,然后将$ok设置为false
第二步:
看到invalidateTags方法中的这个$this->pool->saveDeferred($item),可以$this->pool可以修改然后就能任意调用类调用saveDeferred方法
但是在他的construct方法中又要求了他带是AdapterInterface接口的
class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterface, PruneableInterface, ResettableInterface全局搜索一下saveDeferred方法,需要是AdapterInterface接口的
在PhpArrayAdapter.php文件中 找到一个saveDeferred方法,追进 看看
public function saveDeferred(CacheItemInterface $item) { if (null === $this->values) { $this->initialize(); } return !isset($this->keys[$item->getKey()]) && $this->pool->saveDeferred($item); }一个公共的saveDeferred方法,里面参数为CacheItemInterface $item, CacheItemInterface是一个接口,然后$item是个参数,只允许这个接口的对象来填入,就相当于$item是个坑位,而这个坑位指定了只能由符合CacheItemInterface要求的对象来填入,触发了是一个if语句,需要让本类的values属性强等于为空,那么就会调用本类的initialize方法,又是一环,接下来寻找initialize方法,但是这个方法本类中没有定义,而因为trait这个关键词修饰的类中trait PhpArrayTrait
trait这个关键词是php为了解决单继承的问题而特意建立的,在java这种面向对象的语言中,继承都是单继承的,一个类只能继承一个父类,从下面这副流程图中可以看到PhpArrayAdapter这个类的继承关系
既然这里没有肯定到他的父类中寻找,到父类中搜索initialize方法
PhpArrayTrait.php
private function initialize() { if (!file_exists($this->file)) { $this->keys = $this->values = []; return; } $values = (include $this->file) ?: [[], []]; if (2 !== \count($values) || !isset($values[0], $values[1])) { $this->keys = $this->values = []; } else { list($this->keys, $this->values) = $values; } }他是一个私有属性,触发了有一个if语句 ,判断本类的file属性是否存在这个文件,如果不存在,就将空数组赋值给本类的values属性,在赋值给本类的keys属性,然后返回为空,if语句外是一个三元运算符的简写,如果include包含$this->file文件不成功,就返回[[],[]],这里也是最后的结尾,可以进行一个文件包含
捋一下思路
首先是搜索 destruct方法,因为unserialize会触发他,
此时是在这个命名空间namespace Symfony\Component\Cache\Adapter的TagAwareAdapter.php文件下
然后destruct方法触发 会调用本类的commit方法,到了commit方法,会返回调用本类的invalidateTags()方法,然后到了本类的invalidateTags()方法中 ,首先判断本类的deferred属性不能为空,并且是个数组,因为后面会循环遍历成键值对形式,接下来就是控制$this->pool的值,来让他调用对应类中的saveDeferred方法了
此时还是在namespace Symfony\Component\Cache\Adapter命名空间中
接下来就找saveDeferred方法到PhpArrayAdapter.php文件中了,调用了saveDeferred方法触发了会有一个if语句 ,判断本类values属性是否为空,不为空,就调用本类的initialize方法,因为他本类中没有调用,而是根据trait这个关键词修饰的类中的trait PhpArrayTrait,在他的父类PhpArrayTrait中寻找 ,找到了发现他可以对本类的file属性进行一个文件包含就此到达终点,因为看wp了所以知道flag在/flag文件中,所以file的值就为/flag了
3.开始构造
那么跟着上面的思路来进行构造payload
调用链:
TagAwareAdapter :: destruct --> TagAwareAdapter :: commit --> TagAwareAdapter :: invalidateTags --> PhpArrayAdapter::saveDeferred --> PhpArrayTrait::initialize (include文件包含成功)
关键点就是$this->poo成为PhpArrayAdapter对象,然后会触发saveDeferred方法,然后转到PhpArrayTrait.php,的initialize方法的include成功执行
分析下exp的中的命名空间意思,首先第一个命名空间Symfony\Component\Cache,涉及的是CacheItem类,为了填充deferred数组,骗过类型检查, Symfony\Component\Cache\Adapter 涉及的TagAwareAdapter和PhpArrayAdapter类,他俩的命名空间要对应上
然后生成的GET传参上去,得到flag
4.知识点
这题其实链子不是特别难,跟着wp也挺好捋清楚的,主要就是命名空间的问题,好绕
PHP命名空间(namespace)是一种机制,用于将代码封装在一个逻辑单元中,从而避免不同代码段之间的命名冲突。简单来说,命名空间就像一个"容器",它可以包含类、函数和常量等元素。通过使用命名空间,我们可以为这些元素提供一个唯一的标识符,即使他们的名字相同也不会冲突
命名空间其实就像我们平时使用的绝对路径一样,只不过是在类名前面加了一个斜杠\,这就叫完全限定名称,不管你当前身处哪个命名空间,只要从\开始写,php就会从最顶层开始找
基本语法
在php中,使用namespace关键字来定义命名空间。命名空间可以嵌套,形成多级结构。
use也可以说是引用吧,就是每次如果不使用use,没使用一个类就要写他的完全限定名称,代码会变的非常 冗长,使用了之后就直接可以进行简写了
还有就是编译代码的时候起作用,告诉PHP编译器,只要看到Profile这个词就把他当成
useApp\Modules\User\Member\Profile,还可以使用as来对他进行一个改名字
implements是接口的关键词,就算是一个规范,接口定义了一组方法名,但没有具体内容。当一个类implements实现了这个接口,他就必需完成接口里所要求的所有任务
简介一下,抽空好好学习一下这个
参考链接:
https://blog.csdn.net/zhaoxilengfeng/article/details/144274431
https://www.runoob.com/php/php-namespace.html
wp:
https://blog.csdn.net/cjdgg/article/details/114873555
https://xz.aliyun.com/news/5429#toc-3
https://cn-sec.com/archives/942151.html
https://syunaht.com/p/3199619840.html
https://mixbp.github.io/2025/05/29/CISCN2019%E6%80%BB%E5%86%B3%E8%B5%9BDay1Web4-Laravel1/