0x00 前言
这几天做了两道有关php反序列化字符逃逸的题目,故来自己本地实验总结一下,以后可以时不时拿起来看.
这种题目有个共同点:
- php序列化后的字符串经过了替换或者修改,导致字符串长度发生变化.
- 总是先进行序列化,再进行替换修改操作.
经典题目:
- [0CTF 2016]piapiapia (替换变长)
- [安洵杯 2019]easy_serialize_php (替换变短)
0x01 漏洞浅析
第一种情况:替换修改之后导致序列化字符串长度变长
- 先看一段php序列化的代码(本代码基于7.0.9版本的php,经过测试较低版本的php会使"被转义导致实验失败:
- 代码功能很简单,输入name,并和sign一同传入到user数组中,user数组序列化后的字符串经过test函数检测之后,输出反序列化之后的结果.
- 关于php反序列化引擎在进行反序列化的时候是以字符长度来进行判断的,关于这一点,前言中的参考文章已经解释的很详细了,这里不再赘述.
- 下面进行漏洞测试,通过修改name的值从而改变sign的值.
- 关键点在于test函数,这个函数检测并替换了非法字符串,看似增加了代码的安全系数,实则让整段代码更加危险.test函数中检测序列化后的字符串,如果检测到了非法字符'x',就把它替换为'ha'.
- 如果输入name=evALx
- 可以看到被替换成了'evALha',这个字符串长度为6,从而导致反序列化失败,无法输出结果.利用这个漏洞,就可以对sign的值进行修改.输入name=evALxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";i:1;s:14:"hello hackerrr";}
- 可以就看到sign的值被成功修改.
- 原因分析
- test函数把'x'替换成'ha',换句话来说,就是把一个字符替换成了两个字符,造成了序列化字符串的"膨胀".
- 首先来考虑要注入的数据,在这个例子中把sign替换为"hello hackerrr",这个字符串在本实验的序列化结果是i:1;s:14:"hello hackerrr,由于要闭合name的双引号以及结束的花括号,所以payload应该是";i:1;s:14:"hello hackerrr";}
- 下面来考虑长度溢出,payload的字符串的长度是29,所以要在name中输入29个x,再加上evAL,整体长度就是62,在经过test函数替换之后,x被替换成了ha,如下图所示:
- 溢出的部分成功逃逸,经过双引号闭合name,以及闭合结束时的花括号,导致sign被成功修改.
第二种情况:替换之后导致序列化字符串长度变短
-
本地实验代码如下:
-
输入name和sign,number值是固定的'2020',经过[序列化->敏感字替换为空(长度变短)->反序列化]的过程之后再输出结果.
-
通过输入name和sign修改number:
payload:name=testtesttesttesttesttest&sign=hello";s:4:"sign";s:4:"eval";s:6:"number";s:4:"2000";}
-
原因分析:
在str_rep函数中如果检测到'php','test'关键字就把其替换为空,那么就利用这一点,我们故意输入敏感字符,替换为空之后来实现字符逃逸.(注意考虑闭合问题)