本文会先容一种新的在 PHP7 中利用默认的 OPcache 引擎履行攻击的办法。利用此攻击向量,攻击者可以绕过“Web 目录禁止文件读写”的限定 ,也可以实行他自己的恶意代码。
0x00 OPcache 利用办法简介OPcache 是 PHP 7.0 中内建的缓存引擎。它通过编译 PHP 脚本文件为字节码,并将字节码放到内存中。
利用 PHP7 加速 Web 运用
OPcache 缓存文件格式请看这里。
同时,它在文件系统中也供应了缓存文件。在 PHP.ini 中配置如下,你须要指定一个缓存目录:
opcache.file_cache=/tmp/opcache
在指定的目录中,OPcache 存储了已编译的 PHP 脚本文件,这些缓存文件被放置在和 Web 目录同等的目录构造中。如,编译后的 /var/www/index.php 文件的缓存会被存储在 /tmp/opcache/[system_id]/var/www/index.php.bin 中。
system_id是当前 PHP 版本号,Zend 扩展版本号以及各个数据类型大小的 MD5 哈希值。在最新版的 Ubuntu(16.04)中,system_id是通过当前 Zend 和 PHP 的版本号打算出来的,其值为 81d80d78c6ef96b89afaadc7ffc5d7ea。这个哈希值很有可能被用来确保多个安装版本中二进制缓存文件的兼容性。当 OPcache 在第一次缓存文件时,上述目录就会被创建。在本文的后面,我们会看到每一个 OPcache 缓存文件的文件头里面都存储了system_id。故意思的是,运行 Web 做事的用户对 OPcache 缓存目录(如:/tmp/opcache/)里面的所有子目录以及文件都具有写权限。
$ ls /tmp/opcache/drwx------ 4 www-data www-data 4096 Apr 26 09:16 81d80d78c6ef96b89afaadc7ffc5d7ea
正如你所看到的,www-data 用户对 OPcache 缓存目录有写权限,因此,我们可以通过利用一个已经编译过的 webshell 的缓存文件更换 OPcache 缓存目录中已有的缓存文件来达到实行恶意代码的目的。
0x01 OPcache 利用场景要利用 OPcache 实行代码,我们须要先找到 OPcache 的缓存目录(如:/tmp/opcache/[system_id])以及 Web 目录(如:/var/www/)。假设,目标站点已经存在一个实行 phpinfo() 函数的文件了。通过这个文件,我们可以得到 OPcache 缓存目录, Web 目录,以及打算system_id所需的几个字段值。我写了一个脚本,可以利用 phpinfo() 打算出 system_id。
其余还要把稳,目标站点必须存在一个文件上传漏洞。假设 php.ini 配置 opcache 的选项如下:
opcache.validate_timestamp = 0 ; PHP 7 的默认值为 1opcache.file_cache_only = 1 ; PHP 7 的默认值为 0opcache.file_cache = /tmp/opcache
此时,我们可以利用上传漏洞将文件上传到 Web 目录,但是创造 Web 目录没有读写权限。这个时候,就可以通过更换 /tmp/opcache/[system_id]/var/www/index.php.bin 为一个 webshell的二进制缓存文件运行 webshell。
在本地创建 webshell 文件 index.php ,代码如下:```<?phpsystem($_GET['cmd']);?>```2. 在 PHP.ini 文件中设置 opcache.file_cache 为你所想要指定的缓存目录运行 PHP 做事器(php -S 127.0.0.1:8080) ,然后向 index.php 发送要求(wget 127.0.0.1:8080),触发缓存引擎进行文件缓存。打开你所设置的缓存目录,index.php.bin 文件即为编译后的 webshell 二进制缓存文件。修正 index.php.bin 文件头里的system_id为目标站点的system_id。在文件头里的署名部分的后面便是system_id的值。通过上传漏洞将修正后的 index.php.bin 上传至 /tmp/opcache/[system_id]/var/www/index.php.bin ,覆盖掉原来的 index.php.bin重新访问 index.php ,此时就运行了我们的 webshell
针对这种攻击办法,在 php.ini 至少有两种配置办法可以防御此类攻击。
禁用file_cache_only启用validate_timestamp0x02 绕过内存缓存(file_cache_only = 0)如果内存缓存办法的优先级高于文件缓存,那么重写后的 OPcache 文件(webshell)是不会被实行的。但是,当 Web 做事看重启后,就可以绕过此限定。由于,当做事看重启之后,内存中的缓存为空,此时,OPcache 会利用文件缓存的数据添补内存缓存的数据,这样,webshell 就可以被实行了。
但是这个方法比较鸡肋,须要做事看重启。那有没有办法不须要做事看重启就能实行 webshell 呢?
后来,我创造在诸如 WordPress 等这类框架里面,有许多过期不用的文件依旧在发布的版本中能够访问。如: registration-functions.php
由于这些文件过期了,以是这些文件在 Web 做事器运行时是不会被加载的,这也就意味着这些文件没有任何文件或内存的缓存内容。这种情形下,通过上传 webshell 的二进制缓存文件为 registration-functions.php.bin ,之后要求访问 /wp-includes/registration-functions.php ,此时 OPcache 就会加载我们所上传的 registration-functions.php.bin 缓存文件。
0x03 绕过期光戳校验(validate_timestamps = 1)如果做事器启用了韶光戳校验,OPcache 会将被要求访问的 php 源文件的韶光戳与对应的缓存文件的韶光戳进行比拟校验。如果两个韶光戳不匹配,缓存文件将被丢弃,并且重新天生一份新的缓存文件。要想绕过此限定,攻击者必须知道目标源文件的韶光戳。如上面所说的,在 WordPress 这类框架里面,很多源文件的韶光戳在解压 zip 或 tar 包的时候都是不会变的。
把稳不雅观察上图,你会创造有些文件从2012年之后从没有被修正过,如:registration-functions.php 和 registration.php 。因此,这些文件在 WordPress 的多个版本中都是一样的。知道了韶光戳,攻击者就可以绕过 validate_timestamps 限定,成功覆盖缓存文件,实行 webshell。二进制缓存文件的韶光戳在 34字节偏移处。
0x04 总结
OPcache 这种新的攻击向量供应了一些绕过限定的攻击办法。但是它并非一种通用的 PHP 漏洞。随着 PHP 7.0 的遍及率不断提升,你将很有必要审计你的代码,避免涌现上传漏洞。并且检讨可能涌现的危险配置项。