利用PHP 7中的OPcache来实现Webshell

TaneshaMadd 8年前
   <p>在这篇文章中,我们将会对PHP7 OPcache引擎中的安全问题进行讲解,而且还会给大家介绍一种新型的漏洞利用技术。通过这种攻击方法,我们可以绕过某些安全强化技术,例如 <a href="/misc/goto?guid=4958822738092762946" rel="nofollow,noindex"> 禁止web目录的文件读写 </a> 等安全保障措施。除此之外,攻击者还可以利用这种攻击技术在目标主机中执行恶意代码。</p>    <h2>OPcahce</h2>    <p>OPcache是PHP 7.0中内嵌的新型缓存引擎。它可以对PHP脚本代码进行编译,并且将编译结果以字节码的形势存入内存中。</p>    <p>OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能, 存储预编译字节码的好处就是省去了每次加载和解析 PHP 脚本的开销。</p>    <p><img src="https://simg.open-open.com/show/e3176bb4e66f216f5b7c58a838d0452d.png"></p>    <p>除此之外,它还能提供文件系统的缓存功能,但是我们必须在PHP.ini配置文件中定义缓存信息的目标文件夹路径:</p>    <p>opcache.file_cache=/tmp/opcache</p>    <p>在上面这个文件夹中,OPcache会将编译好的PHP脚本保存在相应PHP脚本的相同目录结构之中。比如说,需要进行编译的代码保存在/var/www/index.php之中,而完成编译的代码将会保存在/tmp/opcache/[system_id]/var/www/index.php.bin之中。</p>    <p>在上述文件路径中,system_id是一个包含了当前PHP版本信息,Zend框架的扩展ID,以及各种数据类型信息的MD5 哈希值。在最新发行版的Ubuntu操作系统(16.04)之中,system_id是由当前Zend框架和PHP的版本号所组成的(81d80d78c6ef96b89afaadc7ffc5d7ea)。当OPcache首次对这些文件进行缓存处理时,会在文件系统中创建相应的目录。</p>    <p>正如我们将会在接下来的章节中看到的,每一个OPcache文件还会在文件的header域中保存system_id的副本。</p>    <p>至此,我们就不得不提到OPcache文件夹了,其中一个非常有趣的地方就是,当用户启用了这项服务之后,用户就会拥有OPcache生成的所有文件夹或者文件(所有文件和文件夹均位于/tmp/opcache/目录之下)的写入权限。</p>    <p>OPcache文件夹中的权限信息如下所示:</p>    <p>$ ls /tmp/opcache/</p>    <p>drwx------ 4 www-data www-data 4096 Apr 26 09:16 81d80d78c6ef96b89afaadc7ffc5d7ea</p>    <p>正如我们看到的那样,www-data分组下的用户都拥有OPcache所生成文件夹的写入权限。如果我们拥有OPcache目录的写入权限,那么我们就可以重写目录中的缓存文件,然后利用webshell来执行任意代码。</p>    <h2>攻击场景</h2>    <p>首先,我们必须得到缓存文件夹的保存路径(/tmp/opcache/[system_id]),以及目标PHP文件的保存路径(/var/www/...)。</p>    <p>为了让大家更容易理解,我们假设网站目录中存在一个phpinfo()文件,我们可以从这个文件中获取到缓存文件夹和文件源代码的存储位置,当我们在计算system_id的时候将会需要用到这些数据。我们已经开发出了一款能够从网站phpinfo()中提取信息,并计算system_id的工具。你可以在我们的 <a href="/misc/goto?guid=4959671862999745870" rel="nofollow,noindex"> GitHub代码库 </a> 中获取到这个工具。</p>    <p>在此,我们需要注意的是,目标网站必须不能对上传的文件有所限制。</p>    <p>现在,我们假设php.ini中除了默认的设置信息之外,还添加有下列配置数据:</p>    <p>opcache.validate_timestamp = 0    ; PHP 7's default is 1</p>    <p>opcache.file_cache_only = 1       ; PHP 7's default is 0</p>    <p>opcache.file_cache = /tmp/opcache</p>    <p>接下来,我们就会给大家讲解攻击的实施过程:</p>    <p>我们已经在网站中找到了一个漏洞,这个漏洞意味着网站不会对我们上传的文件进行任何的限制。我们的目标就是利用包含后门的恶意代码替换掉文件/tmp/opcache/[system_id]/var/www/index.php.bin。</p>    <p><img src="https://simg.open-open.com/show/6dcc7bf2070ec9052232d3c6c107e896.png"></p>    <p>上图显示的就是包含漏洞的网站界面。</p>    <p>1.在本地创建一个包含Webshell的恶意PHP文件,将其命名为“index.php”:</p>    <p><?php</p>    <p>system($_GET['cmd']);</p>    <p>?></p>    <p>2.将opcache.file_cache的相关设置添加进你的PHP.ini文件之中。</p>    <p>3.利用php –S 127.0.0.1:8080命令启动一个Web服务器,然后向服务器请求index.php文件,并触发缓存引擎。在这一步中,我们只需要使用命令wget 127.0.0.1:8080就可以实现我们的目的了。</p>    <p>4.定位到我们在第一步中设置的缓存文件夹,你将会发现了一个名为index.php.bin的文件。这个文件就是已经经过编译处理的webshell。</p>    <p><img src="https://simg.open-open.com/show/e07193e604651c1480d74621b4c1887e.png"></p>    <p>上图显示的是OPcache生成的index.php.bin。</p>    <p>5.由于本地system_id很可能与目标主机的system_id不同,所以我们必须打开index.php.bin文件,并将我们的system_id修改成目标主机的system_id。正如我们之前所提到的,system_id是有可能被猜到的,例如暴力破解,或者根据phpinfo()文件中的服务器信息计算出来。我们可以在文件签名数据之后修改system_id,具体如下图所示:</p>    <p><img src="https://simg.open-open.com/show/a9a7f351ba77c450a047b8bc6a3c3582.png"></p>    <p>上图显示的是system_id的数据存储位置。</p>    <p>6.由于目标网站对上传的文件没有任何的限制,所以我们现在就将文件上传至服务器的目录之中:</p>    <p>/tmp/opcache/[system_id]/var/www/index.php.bin</p>    <p>7.刷新网站的index.php,网站将会自动执行我们的webshell。</p>    <p><img src="https://simg.open-open.com/show/2c9666a41e5c8b97e2c1d56e184f4511.png"></p>    <p>绕过内存缓存(file_cache_only = 0)</p>    <p>如果内存缓存的优先级高于文件缓存,那么重写OPcache文件并不会执行我们的webshell。如果服务器托管的网站中存在文件上传限制漏洞,那么在服务器重启之后,我们就可以绕过这种限制。既然内存缓存可以被清空,OPcache将会使用文件缓存来填充内存缓存,从而达到我们执行webshell的目的。</p>    <p>这也就意味着,我们还是有办法能够在服务器不进行重启的情况下,执行我们的webshell。</p>    <p>在WordPress等网站框架之中,还是会有一些过时的文件可以公开访问到,例如 <a href="/misc/goto?guid=4959671863092830357" rel="nofollow,noindex"> registration-functions.php </a> 。</p>    <p>由于这些文件已经过时了,所以系统不会再加载这些文件,这也就意味着,内存和文件系统的缓存中是不可能存在有这些文件的。当我们上传了恶意代码(registration-functions.php.bin)之后,然后访问相关的网页(/wp-includes/registration-functions.php),OPcache就会自动执行我们的webshell。</p>    <p>绕过时间戳认证(validate_timestamps = 1)</p>    <p>时间戳通常是一个字符序列,它可以唯一地标识某一时刻的时间。一般来说,时间戳产生的过程为:用户首先将需要加时间戳的文件用Hash编码加密形成摘要,然后将该摘要发送到DTS,DTS在加入了收到文件摘要的日期和时间信息后再对该文件加密(数字签名),然后送回用户。</p>    <p>如果服务器启用了时间戳认证功能,OPcache将会对被请求的PHP源文件的时间戳进行验证,如果该文件与缓存文件header域中的时间戳相匹配,那么服务器就会允许访问。如果时间戳不匹配,那么缓存文件将会被丢弃,并创建出一个新的缓存文件。为了绕过这种限制,攻击者必须知道目标源文件的时间戳。</p>    <p>这也就意味着,在WordPress等网站框架之中,源文件的时间戳是可以获取到的,因为当开发人员将代码文件从压缩包中解压出来之后,时间戳信息仍然是保持不变的。</p>    <p><img src="https://simg.open-open.com/show/a12c96408bb621a787d90e8731f4ce30.png"></p>    <p>上图显示的是WordPress/wp-includes文件夹中的信息。</p>    <p>有趣的是,其中的有些文件从2012年起就再也没有进行过任何的修改(请注意以下两个文件:registration-functions.php和registration.php)。因此,即使是不同版本的WordPress,这些相同文件的时间戳也是一样的。在获取到了文件时间戳的信息之后,攻击者就可以修改他们的恶意代码,并且成功覆盖服务器的缓存数据。时间戳信息位于文件开头处的第34字节位置:</p>    <p><img src="https://simg.open-open.com/show/b7ed4387b223031f4e44f2af3e477631.png"></p>    <h2>演示视频</h2>    <p>在此,我们给大家提供了一个简短的演示视频,并在视频中对攻击步骤进行了讲解:</p>    <p>视频地址:https://youtu.be/x42l-PQHhbA</p>    <p>正如我们在此之前提到的,大家可以在我们的 <a href="/misc/goto?guid=4959671862999745870" rel="nofollow,noindex"> GitHub代码库 </a> 中获取到你们所需要的工具。</p>    <h2>总结</h2>    <p>总而言之,这种新型的攻击方法并不会对使用PHP进行开发的应用程序产生影响,因为这并不是PHP的通用漏洞。现在,很多Linux发行版的操作系统(例如Ubuntu 16.04)都会默认安装PHP 7,所以当我们在了解到了这种攻击技术之后,在我们的开发过程中,更加因该谨慎地审查我们的代码,并检查网站中是否存在文件上传限制漏洞,因为这个漏洞将会对服务器的安全产生影响。</p>    <p>本文由 360安全播报 翻译,转载请注明“转自360安全播报”,并附上链接。</p>    <p><a href="/misc/goto?guid=4959671863188799457" rel="nofollow,noindex">原文链接:http://blog.gosecure.ca/2016/04/27/binary-webshell-through-opcache-in-php-7/</a></p>