11/5
文章共计4381个词
预计阅读10分钟
来和我一起阅读吧
前言
php反序列化的字符逃逸算是比较难理解的一个知识点,在最近的好几场比赛中都出现了相关的题,于是下定决心彻底理解透彻这个知识点,于是便有了这篇文章。
基础知识理解
字符逃逸在理解之后就能够明白,这是一种闭合的思想,它类似SQL中的万能密码,理解这种原理之后会变得特别容易。
在SQL注入中,我们常用'
、"
来对注入点进行一些闭合或者一些试探,从而进行各种姿势的注入,反序列化时,序列化的值是以;
作为字段的分隔,在结尾是以}
结束,我们稍微了解一下,
<?php
class people{
public $name = 'Tom';
public $sex = 'boy';
public $age = '12';
}
$a = new people();
print_r(serialize($a));
O:6:"people":3:{s:4:"name";s:3:"Tom";s:3:"sex";s:3:"boy";s:3:"age";s:2:"12";}
反序列化的过程就是碰到;}
与最前面的{
配对后,便停止反序列化。我们可以将上面的序列化的值稍作改变:
O:6:"people":3:{s:4:"name";s:3:"Tom";s:3:"sex";s:3:"boy";s:3:"age";s:2:"12";}123123
可以看到,并没有报错,而且也顺利将这个对象反序列化出来了,恰好说明了我们以上所说的闭合的问题,与此同时,修改一些序列化出来的值可以反序列化出我们所知道的对象中里没有的值,在学习绕过__wakeup
就能过知道了,这里可以自己去做一些尝试,去理解。
接下来就是要说到报错的时候了,当你的字符串长度与所描述的长度不一样时,便会报错,比如上图中s:3:"Tom"
变成s:4:"Tom"
或s:2:"Tom"
便会报错,为的就是解决这种报错,所以在字符逃逸中分为两类:
-
字符变多
-
字符减少
关键字符增多
在题目中,往往对一些关键字会进行一些过滤,使用的手段通常替换关键字,使得一些关键字增多,简单认识一下,正常序列化查看结果
这里,我们对序列化后的字符串进行了替换,使得后来的反序列化报错,那我们就需要在Tom
这里面的字符串做手脚,在username
之后只有一个age
,所以在双引号里面可以构造我需要的username
之后参数的值,这里修改age
的值,我们这里将Tom
替换为Tom";s:3:"age";s:2:"35";}
然后进行反序列化,这里指的是在对username传参的时候进行修改,也就是我们写链子的时候进行的操作
可以看到构造出来的序列化字符串长度为25,而在上面的反序列化过程中,他会将一个o变成两个,oo,那么得到的应该就是s:25:"Toom"我们要做的就是让这个双引号里面的字符串在过滤替换之后真的有描述的这么长,让他不要报错,再配合反序列化的特点,(反序列化的过程就是碰到;}
与最前面的{
配对后,便停止反序列化)闭合后忽略后面的age:13的字符串成功使得age被修改为35。
而age的修改需要前面的字符串username的值长度与描述的一样,这需要我们精确的计算,这里是将一个o变成两个,以下就只写o不写Tom,效果一致,我们需要知道我们除了双引号以内的,所构造的字符串长度为多少,即";s:3:"age";s:2:"35";}
的长度22,那就需要22个o,
总的来说就是22个o加上后面的字符串长度22,总长度就为44,在被过滤替换后,光o就有44个,符合描述的字符串长度。下面就说明(为什么叫做逃逸)
这里特意写了"
将一大串o进行与前面的"
闭合了,如果直接反序列化,在序列化出来的值中就包含了";s:3:"age";s:2:"35";}
。
反序列的过程中,所描述的字符串长度(这里为44),而后面双引号包裹的字符串长度(这里为22)不够所描述的长度,那么他将会向后吞噬,他会将后双引号吞噬,直至足够所描述的长度,在一切吞噬结束之后,序列化出来的字符串如果不满足反序列化的字符串的格式,就会报错。我们这里是他吞噬结束后,还满足这个格式,所以不报错。
在这个例子中,我们利用他对序列化后的值,进行增加字符串长度的过滤,让他填充双引号内的字符串达到所描述的44这么长,使得后面的s:3:"age";s:2:"35";
不被吞噬,让这部分代码逃逸出吞噬,又让他提前遇到}
忽略后面的一些不需要的字符串,结束反序列化。
可以看到,我们构造的payload是成功修改了age,这里是数组,在对对象操作时也是一样的。
刚刚说到吞噬,在增加字符串的题目中,我们是利用题中的增加操作,阻止他进行向后吞噬我们构造的代码,而在字符减少的过程中,我们也是利用这个操作。
关键字符减少
有了前面”吞噬“的一种解释,那么字符串减少就很好说了 ,同样的也是因为替换的问题,使得参数可以让我们构造payload
这里的错误是因为s:5:"zddo"
长度不够,他向后吞噬了一个双引号,导致反序列化格式错误,从而报错,我们要做的就是让他往后去吞噬一些我们构造的一些代码。以下讲具体实施。
同样的,我们这里以修改age为例,不同的是与增加字符串传值的地方有些许不同,我们构造的值是有一部分让他吞噬的
先正常传递值序列化出我们需要修改的值,我们需要的是将age:13改为35
取出";s:3:"age";s:2:"35";}
这就是我们需要构造的,接着继续将这部分内容重新传值,序列化出来,得到下面的结果
选中部分就是我们构造出来,他需要吞噬的代码,s:22:""
这个双引号里面我们还有操作的空间,用来补齐字符串长度,接着就是计算我们自己所需要吃掉的字符串长度为18,根据过滤,他是将两个o变成一个,也就是每吃掉一个字符,就需要有一个oo,那我们需要吃掉的是18个长度,那么我们就需要18个oo,在吞噬结束之后我们的格式又恢复正确,使得真正的字符s:3:"age";s:2:"35";
逃逸出来,成功加入反序列化
这就是我们最终的payload,可以看到下图成功修改了
例题
有了以上基础,就可以做题了,简单的开始入手
安H四月(字符减少)
<?php
show_source("index.php");
function write($data) {
return str_replace(chr(0) . '*' . chr(0), ' ', $data);
}
function read($data) {
return str_replace(' ', chr(0) . '*' . chr(0), $data);
}
class A{
public $username;
public $password;
function __construct($a, $b){
$this->username = $a;
$this->password = $b;
}
}
class B{
public $b = 'gqy';
function __destruct(){
$c = 'a'.$this->b;
echo $c;
}
}
class C{
public $c;
function __toString(){
echo file_get_contents($this->c);
return 'nice';
}
}
$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));
看到上面的代码,很明显,我们需要利用file_get_contents();
读取文件,将flag读取出来,但是他是个__toString()
方法,我们就要让他触发这个方法,当反序列化出对象后,被当作字符串使用时,就可以触发,那我们就需要写一个链,与此同时我们也要知道字符串被删减了几个字符
function write($data) {
return str_replace(chr(0) . '*' . chr(0), ' ', $data);
}
function read($data) {
return str_replace(' ', chr(0) . '*' . chr(0), $data);
}
看这一部分即可了解到,如果发现不可见字符*不可见字符
,字符串就会增多,接着又将
评论