长亭百川云 - 文章详情

浅析PHP反序列化漏洞之PHP常见魔术方法(一) - magic_zero

博客园 - magic_zero

61

2024-07-19

  作为一个学习web安全的菜鸟,前段时间被人问到PHP反序列化相关的问题,以前的博客中是有这样一篇反序列化漏洞的利用文章的。但是好久过去了,好多的东西已经记得不是很清楚。所以这里尽可能写一篇详细点的文章来做一下记录。

  我们来参考这里:

https://secure.php.net/manual/zh/language.oop5.magic.php

我们根据官方文档中的解释,一个一个来进行测试。

    __construct() 和 __destruct()

    __construct()被称为构造方法,也就是在创造一个对象时候,首先会去执行的一个方法。

我写了这样的一个demo来做测试:

class test {

    private $flag = '';
    public $filename = '';
    public $data = '';

    function \_\_construct($filename, $data) {
        $this\->filename = $filename;
        $this\->data = $data;
        echo 'construct function in test class';
        echo "<br>";
    }
}  
$a = new test('test.txt', 'data');

测试结果:

同样的,我们编写一个类的析构方法,__destruct()

析构函数的作用:

代码如下:

class test {

    private $flag = '';
    public $filename = '';
    public $data = '';

    function \_\_construct($filename, $data) {
        $this\->filename = $filename;
        $this\->data = $data;
        echo 'construct function in test class';
        echo "<br>";
    }

    function \_\_destruct() {
        echo 'destruct function in test class';
        echo "<br>";
    }
}

$a = new test('test.txt', 'data');

运行结果:

__set()    __get()    __isset()    __unset()    作用如下:

我们一样是来写一个代码进行验证:

class test {

    private $flag = '';
    
    # 用于保存重载的数据 
    private $data = array();

    public $filename = '';

    public $content = '';

    function \_\_construct($filename, $content) {
        $this\->filename = $filename;
        $this\->content = $content;
        echo 'construct function in test class';
        echo "<br>";
    }

    function \_\_destruct() {
        echo 'destruct function in test class';
        echo "<br>";
    }

    function \_\_set($key, $value) {
        echo 'set function in test class';
        echo "<br>";
        $this\->data\[$key\] = $value;
    }

    function \_\_get($key) {
        echo 'get function in test class';
        echo "<br>";
        if (array\_key\_exists($key, $this\->data)) {
            return $this\->data\[$key\];
        } else {
            return null;
        }
    }

    function \_\_isset($key) {
        echo 'isset function in test class';
        echo "<br>";
        return isset($this\->data\[$key\]);
    }

    function \_\_unset($key) {
        echo 'unset function in test class';
        echo "<br>";
        unset($this\->data\[$key\]);
    }
    
    public function set\_flag($flag) {
        $this\->flag = $flag;
    }

    public function get\_flag() {
        return $this\->flag;
    }
}

$a = new test('test.txt', 'data');

# \_\_set() 被调用
$a\->var = 1;

# \_\_get() 被调用
echo $a\->var;

# \_\_isset() 被调用
var\_dump(isset($a\->var));

# \_\_unset() 被调用
unset($a\->var);

var\_dump(isset($a\->var));

echo "\\n";

运行结果:

我们可以看到调用的顺序为: 构造方法 => set方法(我们此时为类中并没有定义过的一个类属性进行赋值触发了set方法) => get方法 => isset方法 => unset方法 => isset方法 => 析构方法

同时也可以发现,析构方法在所有的代码被执行结束之后进行的。

    __call()    __callStatic()    

官方文档中的解释:

类似以上介绍过的__set()和__get(),刚刚是访问不存在或者不可访问属性时候进行的调用。现在是访问不存在或者不可访问的方法时候:

代码如下:

class test {

    private $flag = '';
    
    # 用于保存重载的数据 
    private $data = array();

    public $filename = '';

    public $content = '';

    function \_\_call($funcname, $args) {
        echo 'function name is: ' . $funcname. ' args is: ' . implode(', ', $args);
        echo "<br>";
    }

    public static function \_\_callStatic($funcname, $args) {
        echo 'static function name is: ' . $funcname. ' args is: ' . implode(', ', $args);
        echo "<br>";
    }
    
    public function set\_flag($flag) {
        $this\->flag = $flag;
    }

    public function get\_flag() {
        return $this\->flag;
    }
}

$obj = new test;

# 调用一个不存在或者无法访问到的方法时候将会调用\_\_call()
$obj\->run('run args, test');

# 调用一个不存在的静态方法,将会去调用\_\_callStatic()
$obj::run('static test');

运行结果:

看文档或者注释应该很明白了。

接下来是对于反序列化漏洞利用最重要的一些方法了。

__sleep()    __wakeup()    __toString()

写个代码来进行验证:

class test {

    private $flag = '';
    
    # 用于保存重载的数据 
    private $data = array();

    public $filename = '';

    public $content = '';

    function \_\_construct($filename, $content) {
        $this\->filename = $filename;
        $this\->content = $content;
        echo 'construct function in test class';
        echo "<br>";
    }

    function \_\_destruct() {
        echo 'destruct function in test class';
        echo "<br>";
    }

    # 反序列化时候触发
    function \_\_wakeup() {
        // file\_put\_contents($this->filename, $this->data);
        echo 'wakeup function in test class';
        echo "<br>";
    }

    # 一般情况用在序列化操作时候,用于保留数据
    function \_\_sleep() {
        echo 'sleep function in test class';
        echo "<br>";
        return array('flag', 'filename', 'data');
    }

    # 当需要输出得到对象名称时候会调用
    function \_\_toString() {
        return $this\->data;
    }
    
    public function set\_flag($flag) {
        $this\->flag = $flag;
    }

    public function get\_flag() {
        return $this\->flag;
    }
}

$key = serialize(new test('test.txt', 'test'));

var\_dump($key);

$b = unserialize($key);

运行结果:

在进行序列化的时候,执行了__sleep()方法,在反序列化的时候执行了__wakeup()方法。

然后是__toString()方法:

class test {

    private $flag = '';
    
    # 用于保存重载的数据 
    private $data = array();

    public $filename = '';

    public $content = '';

    function \_\_construct($filename, $content) {
        $this\->filename = $filename;
        $this\->content = $content;
        echo 'construct function in test class';
        echo "<br>";
    }

    function \_\_destruct() {
        echo 'destruct function in test class';
        echo "<br>";
    }

    # 当需要输出得到对象名称时候会调用
    function \_\_toString() {
        return $this\->content;
    }
}

$a = new test('test.txt', 'data');
echo $a."<br>";

结果:

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2