Pwnhub公开赛出了个简单的PHP代码审计题目,考点有两个:php
若是说仅为了作出题目拿到flag,这个题目太简单,后台也有数十名选手提交了答案和writeup。但深刻研究一下这两个知识点,仍是颇有意思的。html
首先经过简单的目录扫描,找到备份文件index.php.bak。下载后发现文件是通过了混淆加密处理的,大部分同窗是直接网上找了付费解密的网站给解的,也有少数几我的说明了解密方法,我挑几种方法说一下。git
这种方法我最佩服了,做者甚至给出了解密脚本,文章以下: http://sec2hack.com/web/phpjiami-decode.html 。github
我本身在出题目以前也进行过度析,但后面并无耐心写一个完整的脚本出来,因此我十分佩服这个做者。web
咱们分析phpjiami后的文件,能够看到他有以下特色:shell
之因此函数名、变量名能够变成“乱码”,是由于PHP的函数名、变量名是支持除了特殊符号之外大部分字符的,好比汉字等。利用这一特色,phpjiami就将全部正常的英文变量给转换了一下形式,其实没有什么特别的奥秘。api
那么,为了方便分析,咱们能够想办法再将其转换回英文和数字。好比,做者使用的是 http://zhaoyuanma.com/phpcodefix.html 对混淆过的代码进行美化;而我是使用 https://github.com/nikic/PHP-Parser 对整个代码进行告终构化的分析,并将全部变量和函数名进行了美化。数组
方法一的好处是我不须要写任何代码,就能够大体进行美化,但显然,美化后的代码是有错误的,原文中也提到了这一点;方法二,虽然须要本身写代码,但美化后的代码没有语法错误,看起来更加直观,而且我还能进一步的进行美化,好比将字符串中的乱码转换成\x
的形式。安全
我美化后的代码以下:网络
后续的操做和上文也差很少,经过源码的分析,正如上文中所说,phpjiami加密源码的整个流程是:
加密流程:源码 -> 加密处理(压缩,替换,BASE64,转义)-> 安全处理(验证文件 MD5 值,限制 IP、限域名、限时间、防破解、防命令行调试)-> 加密程序成品,再简单的说:源码 + 加密外壳 == 加密程序 (该段出处)
因此,其实这种方法并无对源码进行混淆,只是对“解密源码的壳”进行了混淆。因此你看到的中文变量、中文函数,实际上是一个壳,去掉这层壳,我能够拿到完整的PHP源码。
因此呀,后台提交的writeup里,有的同窗想固然地认为修改eval为echo就能输出源码了……实际上根本没实际试过,改动文件是会致使不能运行的;还有同窗认为这里仅是将源码混淆为用户体验极差的代码,致使人眼没法阅读,并无理解这里其实混淆的不是源码。
0x01中说到的方法当然是很美好的,可是假如加密者随意改动一点加密的逻辑,可能致使咱们须要从新分析加密方法,写解密脚本。咱们有没有更通用的方法?
HOOK EVAL应该是被提到过最多的方法,我也看到了Medici.Yan发布的一篇文章: http://blog.evalbug.com/2017/09/21/phpdecode_01/
我前文说过,phpjiami实际上是只是混淆了壳,这个壳的做用是执行真正的源码。那么,执行源码必然是会通过eval之类的“函数”(固然也不尽然),那么,若是咱们可以有办法将eval给替换掉,不就能够得到源码了么?
遗憾的是,若是咱们仅仅简单地将eval替换成echo,将致使整个脚本不能运行——由于phpjiami检测了文件是否被修改。
那么,咱们能够寻求更底层的方法。就是不少人之前提到过的,将PHP底层的函数 zend_compile_string
给拦截下来,并输出值。Medici.Yan的文章中说的很清楚,也给出了参考文档和源码,我就再也不赘述了。
我本身简单写了一个扩展,并用php5.6编译: https://drive.google.com/open?id=0B4uxE69uafD5anVTZ1VwNXN0WEU
下载之,在php.ini中添加extension=hookeval.so
,而后直接访问加密过的php代码便可(当时参考tool.lu的站长xiaozi的代码 http://type.so/c/php-dump-eval.html ,因此分隔符里有关键字):
16年kuuki曾分享过一个在线解密的工具: https://xianzhi.aliyun.com/forum/read/64.html ,但测试了一下phpjiami解密不了。缘由是,phpjiami在解密的时候会进行验证:
php_sapi_name() == 'cli' ? die():'';
因此若是这个源码是在命令行下运行,在执行这条语句的时候就die了。因此,即便你编译好了hookeval.so并开启了这个扩展,也须要在Web环境下运行。
提升篇:有没有什么简单的办法在命令行下也能模拟web环境呢?方法我先不说,你们能够本身思考思考。
那么有的同窗说:php扩展太难了,我不会写C语言,怎么办?
不会写C语言也不要紧,你只须要会写PHP便可。这是我凤凰师傅提到的一个方法,也是我理想中的一个解,很是简单,两行代码搞定,解密用时比你去网上花钱解密还短:
<?php include "index.php"; var_dump(get_defined_vars());
原理其实也很简单。phpjiami的壳在解密源码并执行后,遗留下来一些变量,这些变量里就包含了解密后的源码。
虽然咱们不能直接修改index.php,将这些变量打印出来,可是咱们能够动态包含之,并打印下全部变量,其中一定有咱们须要的源码(var_dump
输出的不完整,只是用它举个例子):
固然,这个方法虽然简单,但有个很严重的问题:假如在执行源码的过程当中exit()
了,咱们就执行不到打印变量的地方了。
因此,这个方法并不必定适用于全部情景,但对于本题来讲,已经足够了。
那么,若是咱们遇到0x03解决不了的状况怎么办?
这时候就要祭出动态调试武器了。尽管加密后的文件看起来乱七八糟,但其仍然是一个符合php语法的php文件,那么咱们就能够直接利用动态调试工具进行单步调试,拿到源码。
简单拿xdebug进行调试,不停单步调试后,就能够发现咱们须要的源码已经在上下文变量中的:
右键“复制值”,便可拿到源码。这也算一个比较简单的方法了。
固然,假若有一天phpjiami修改了混淆流程,源码再也不储存于变量中,那么就须要分析一下代码执行的流程。所谓万变不离其中,最终断在eval的那一步,必定有你须要的源码。
后面的部分反而比较简单。拿到index.php的源码后,发现其包含了FileUpload.class.php,因此再次下载这个文件的源码进行解密。
分析FileUpload类,发现其取后缀有两种方式:将文件名用.
分割成数组$arr
,一是用$arr[count($arr)-1]
的方式取数组最后一个元素,二是用end($arr)
的方式取数组最后一个元素。
正常来讲,字符串用.
分割成的数组,用这两种方法取到的末元素应该是相同的。但取文件名的时候,若是咱们已经传入的是数组,则不会再次进行分割:
$filename = $_POST[...]; if(!is_array($filename)) { $filename = explode('.', $filename); }
也就是说我能控制$filename
这个数组。因此,我只须要找到$arr[count($arr)-1]
和end($arr)
的区别,便可绕事后缀检查。
显然,前者是取根据数组下标来取的值,后者取的永远是数组里最后一个元素。因此,咱们只须要让下标等于count($arr)-1
的元素不是数组最后一个元素便可。
好比:[1=>'gif', 0=>'php']
或者['0'=>'abc', '2'=>'gif', '100'=>'php']
。
最后想说一句话:不求甚解是阻碍部分人进步的一大阻力。共勉。