只有透彻地理解底层,才能创造奇迹。
这道题有一个平台复现了出来,可供大家练习,可以访问如下网址:
http://web.jarvisoj.com:32784/
刚开始看到这道题目,我是懵逼的。因为整篇代码没有数据输入口,然后怀疑有其它机关,抓包、扫目录无果之后,找到了一篇writeup如下:
https://chybeta.github.io/2017/07/05/jarvisoj-web-writeup/#PHPINFO
了解了思路和背景知识之后,仿佛感觉开启了通向“新世界”的大门~
这是一道纯代码审计的题目,没有其它的猫腻,但却需要对PHP反序列化机制的理解很深,不然就像我的第一反应一样,“这不没漏洞嘛~”。
这个漏洞的关键点在于:
ini_set('session.serialize_handler', 'php');
PHP内置了多种处理器,用于存取$_SESSION的时候对数据进行序列化和反序列化,这个的意思是在于设置序列化解释格式,我的理解是和字符集相似,按照某种格式构造和解析序列化的字段。
漏洞产生在php_serialize和php解析方式上。
如果我们通过php_serialize的方式构造序列化语句,然后通过php的方式解析序列化语句,就会出现问题。原因是在使用php_serialize构造过程中,可以在字符串变量中储存 | 符号,但是如果按照php的方式解析的话,会把 | 之前的语句当做数组的键,之后的语句当做值,这时我们就可以按照这个特性来构造执行对象的命令。
通过php_serialize构造的:
a:1:{s:4:"ryat";s:20:"|O:8:"stdClass":0:{}";}
以php的方式解析会变为:
array(1) { ["a:1:{s:4:"ryat";s:20:""]=> object(stdClass)#1 (0) { }}
成功执行了变量。
这时就有一个问题,在题目代码中,没有某个值是用来接受我们传入的数据,并储存到$_SESSION中的。
鲁迅说过,“没有代码,创造代码也要上!”
其实我们是有办法传入$_SESSION数据的。
我们查看phpinfo页面,可以发现,session.upload_progress.enabled
是被打开了的,而当这个选项被打开时,php会自动记录上传文件的进度,在上传时会将其信息保存在$_SESSION
中。
这时,我们可以在本地构造一个指向目标页面的表单:
上传之后,用burp修改filename,就可以将我们想要传入的序列化字段储存进去。
传入什么呢?因为在php大于5.5.4的版本中默认使用php_serialize规则,所以我们可以在本地构造语句:
将想要传入的数据,传入即可。
知道了原理就可以开始做题了,目前我们掌握的信息很少,当前页面的源码也已经给出。一般来说,最后的flag都会在web根目录下,或者其它页面的源码中,所以我们先尝试获取当前目录下的文件列表.
传入payload等于:
print_r(scandir(dirname(FILE)));
序列化结果为:
O:5:"OowoO":1:{s:4:"mdzz";s:36:"print_r(scandir(dirname(FILE)));";}
抓包,修改filename传过去
发现可疑文件
这时我们查看phpinfo界面,可以发现_SESSION["SCRIPT_FILENAME"]
中标注了index.php所在的目录/opt/lampp/htdocs/
,而我们想要的文件也在里面,没网了……截图传不到图床上,就不做演示了,其实到这一步就很简单了。
这道题给我印象最深的,就是通过filename传入$_SESSION数据。按照固有的思维,源代码中都没有接受口,那就没办法喽。其实很多漏洞,都可以巧妙地通过其它页面去构造、利用。比如二次注入,以及这次的反序列化解析差异漏洞。
如果我在代码审计的时候遇到这种代码,肯定是不知道这里是有漏洞的,也就错过了一个很棒的漏洞。
综上所述,想要成为一个优秀的安全从业人员,第一要务是解放思想,不拘束于固有的思路。第二要务是扩宽知识面,别人不知道的点你却知道,这就是核心竞争力。