PHP序列化和反序列化
前言
一、PHP序列化(serialize)
序列化就是将变量或者对象转换为字符串的过程
#序列化结果:
o:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
//o 即object简写的对象
//1 对象的字符数(这里对象是后面“A”,只有一个字符)
//A 对象名
//2 花括号中的对象数(花括号中总是两个为一对,封号前后两个为一组前为变量名,后为变量值)
//s 表示字符串(string)
//4 变量名长度(这里是后面的name)
//aaaa 对应的变量名,前面name对应值
//后面pass,123456同理
二、PHP反序列化(unserialize)
反序列化是将字符串转换成变量或者对象的过程
三、魔幻函数
_construct() 创建对象时初始化
_destruction() 结束时销毁对象
_tostring() 对象被当做字符串时使用
_sleep() 序列化对象前使用
_wakeup() 反序列对象之前调用
_call() 调用对象不存在时使用
_get() 调用私有属性时使用
四、PHP反序列化字符逃逸
反序列化的字符串都是以“;}结束,如果把”;}带入需要反序列化的字符串中(除结尾处),就能让反序列化提前闭合结束,导致后面的原有内容丢失
1.替换修改后导致序列化字符串变长
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
以上代码的运行过程是:
先序列化AA,然后进行替换将不希望出现的词‘bb‘
转换为替换词‘ccc’
,然后再进行反序列化,最后输出pass
#这里序列化后的代码:
o:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
在反序列化的时候php会根据s所指定的字符长度去读取后面的字符,但读取过程很霸道,如果后面的字符数不够,就会继续向后读取,不论后面是什么符号
o:1:"A":2:{s:4:"name";s:5:"aaaa";s:4:"pass";s:6:"123456";}
//这里如果s指定的字符长度为5,aaaa只有四位就会继续读取将“也读取,而正常是需要”;来闭合变量的,这里只剩下了;就会使闭合出现错误
在一开始的代码中会将‘bb’
替换为‘ccc’
就会使字符量从2变成3
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaabb';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
//这样序列化出来的代码应该为
o:1:"A":2:{s:4:"name";s:6:"aaaabb";s:4:"pass";s:6:"123456";}
**然后经过filter过滤后“aaabb”就会变为“aaaccc”而指定的长度为6,就只能读取到‘aaacc’,最后一个‘c’就读取不到,而这个c就是所谓的字符串逃逸**
那么如果我们将name的变量值改为“;s:4:“pass”;s:4:“hack”;}
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='“;s:4:"pass";s:4:"hack";}';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
此时序列化后是o:1:"A":2:{s:4:"name";s:25:"“;s:4:"pass";s:4:"hack";}";s:4:"pass";s:6:"123456";}
c的变量值输出为
name=“;s:4:"pass";s:4:"hack";}
pass=123456
这里我们想要做到的是要逃逸“;s:4:"pass";s:4:"hack";}
将pass改为hack,
所以我们只要在name之中再加25个‘bb’
这样经过过滤就会变成50个c如下
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb “;s:4:"pass";s:4:"hack";}';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";
$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
此时序列化后是o:1:"A":2:{s:4:"name";s:75:"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc“;s:4:"pass";s:4:"hack";}";s:4:"pass";s:6:"123456";}
c的变量值输出为
name=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc(75个)
pass=hack
想要逃逸的字符有25个所以我们要让name变量值读取在读取到这25个字符前就闭合,前面就加25个‘bb’
,变量名读取的长度就为(25*2+25)75个字符,这样过滤后就变成75个c,刚好读够变量长度,然后逃逸的字符中下一个是;”
是变量名的读取闭合,后面逃逸的s:4:"pass";s:4:"hack";}"
就作为了对pass变量的赋值
2.替换之后导致序列化字符变短
类推替换之后导致序列化字符变长即可推出