利用PHP 7中的OPcache来实现Webshell
作者:admin | 时间:2016-6-3 22:47:05 | 分类:黑客技术 隐藏侧边栏展开侧边栏
在本文中我们将分析通过利用PHP7默认的 OPcache引擎来对漏洞进行利用的技巧。通过这个漏洞利用技巧,我们将能绕过“禁止web目录的文件读写”(http://www.cyberciti.biz/tips/php-security-best-practices-tutorial.html)的防护,还能在主机中执行任意代码。
OPcache
OPcache是PHP 7.0内建的新型缓存引擎,它会编译php的脚本,然后在内存中生成对应的字节码。
它还支持在文件系统中进行缓存,我们可以在PHP.ini中指定缓存目录
opcache.file_cache=/tmp/opcache
在上面指定的目录中,OPcache会将编译好的PHP脚本和对应的PHP脚本放在同一个目录结构之中。比如说,/var/www/index.php所编译的脚本会被保存为/tmp/opcache/[system_id]/var/www/index.php.bin。
这里的system_id是一个包含了当前PHP版本信息,Zend框架的扩展ID和各种数据类型信息的哈希值。在Ubuntu最新版16.04中,system_id是由当前Zend框架和PHP的版本号所组成的(81d80d78c6ef96b89afaadc7ffc5d7ea),这些哈希值有可能是被用来确保二进制兼容性的,这个目录会在OPcache第一次进行缓存时生成。
我们将会在下面看到的是每一个OPcache文件还会在文件的header域中保存system_id的对应的副本。
关于OPcache文件夹最有意思的点就在于,用户启动该服务后就会拥有OPcache生成的所有文件夹/文件(在/tmp/opcache/目录之下)的写入权限。
下面是OPcache文件夹的权限情况
你可以看到OPcache生成的文件夹对用户www-data是可写的,这就导致了我们可以通过重写目录中的缓存文件为webshell,然后执行任意代码。
攻击场景
首先,我们必须获得缓存文件夹的地址(/tmp/opcache/[system_id]),以及目标PHP文件的地址(/var/www/…)。
为了简单起见,我们假设网站存在一个phpinfo()文件,我们可以从这个文件中获取到缓存文件夹和文件源代码的存储地址,还有在计算system_id的时候将会用到的数据(我们已经开发出一款能够通过phpinfo()文件来计算system_id的工具。你可以在这里下载(https://github.com/GoSecure/php7-opcache-override))。
这里还要再提的一点是目标网站不能对文件上传进行限制。
我们假设php.ini中配置的额外数据为:
opcache.validate_timestamp = 0 ; PHP 7's default is 1
opcache.file_cache_only = 1 ; PHP 7's default is 0
opcache.file_cache = /tmp/opcache
接下来,我们来分析一下攻击的过程:
如下图,我们已经在网站中找到了一个任意上传漏洞,并且/var/www/可写,我们的目标就是将后门代码替换到/tmp/opcache/[system_id]/var/www/index.php.bin中。
1、在本地创建一个包含Webshell的PHP文件,将其命名为”index.php”:
<?php
system($_GET['cmd']);
?>
2、配置PHP.ini文件中的opcache.file_cache选项。
3、使用php -S 127.0.0.1:8080命令在本地启动一个Web服务器,通过使用命令wget 127.0.0.1:8080向服务器请求index.php文件来触发缓存引擎。
4、定位到我们在第一步中设置的缓存文件夹,你就会发现一个名为index.php.bin的文件,这个文件就是经过编译处理后的webshell,如下图。
5、由于本地system_id很可能与目标主机的system_id不同,所以我们必须打开index.php.bin文件,并将我们的system_id修改成目标主机的system_id。正如之前所提到的,system_id可以被猜解,例如暴力破解,或者根据phpinfo()文件中的服务器信息计算出来(https://github.com/GoSecure/php7-opcache-override/blob/master/system_id_scraper.py)。我们可以在文件签名之后替换system_id,如下图。
6、利用任意上传漏洞将文件上传至/tmp/opcache/[system_id]/var/www/index.php.bin
7、刷新网站的index.php,网站将会自动执行我们的webshell。
更深入一点
在php.ini中至少有两个配置项可以造成另类的行为
1、禁止file_cache_only
2、允许validate_timestamp
绕过内存缓存(file_cache_only = 0)
如果内存缓存的优先级高于文件缓存,那么重写OPcache文件并不会执行我们的webshell。在上传完webshell后,如果服务器重启之后,我们就可以绕过这种限制。由于内存缓存将被清空,OPcache这时会将文件缓存填充到内存中,从而执行了我们的webshell。
因为上面说到的这种机制的原因,我们将很有可能在服务器不重启的情况下实现webshell的执行。
在WordPress等网站框架之中,还是会有一些过时的文件可以被公开访问到(比如registration-functions.php(https://github.com/WordPress/WordPress/blob/703d5bdc8deb17781e9c6d8f0dd7e2c6b6353885/wp-includes/registration-functions.php))。
由于这些文件已经过时了,所以系统不会再加载这些文件,也不会在内存和文件系统的缓存中有这些缓存的文件。在我们上传了恶意代码(registration-functions.php.bin)之后访问相关的网页(/wp-includes/registration-functions.php),OPcache就会自动执行我们的webshell。
绕过时间戳认证(validate_timestamps = 1)
如果服务器启用了时间戳认证功能,OPcache将会对被请求的PHP源文件的时间戳进行验证。如果该文件的时间戳与缓存文件header域中的时间戳相匹配,那么服务器就会允许访问。反之,缓存文件将会被丢弃,并创建出一个新的缓存文件。为了绕过这种限制,攻击者必须知道目标源文件的时间戳。
这也就意味着,在WordPress等网站框架之中,源文件的时间戳是可以获取到的,因为当开发人员将代码文件从压缩包中解压出来之后,时间戳信息仍然是保持不变的,如下图。
有趣的是,其中的有些文件从2012年起就再也没有进行过任何的修改(请注意registration-functions.php和registration.php文件)。因此,即使是不同版本的WordPress,它们的时间戳也是一样的。在获取到了文件时间戳的信息之后,攻击者就可以修改他们的恶意代码,并且成功覆盖服务器的缓存数据。时间戳位于文件开头的第34字节的位置,如图
Demo
正如我们在此之前提到的,大家可以在我们的GitHub代码库(https://github.com/GoSecure/php7-opcache-override)中下载工具。工具中包含了 010编辑模板、生成SYSTEM_ID的工具还有本篇文章所用到的网页代码。
结论
总之,这种新型的攻击方法针对具体的环境而言的,因为这并不是PHP的通用漏洞。很多操作系统,例如Ubuntu 16.04都会默认安装PHP 7,所以在我们的开发过程中,更加应该谨慎地审查我们的代码,并检查网站中是否存在文件上传漏洞。