浅析PHP反序列化漏洞之PHP常见魔术方法(一)
作为一个学习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>";
结果: