利用 PHP7 的 OPcache 执行 PHP 代码

   2016-08-30 0
核心提示:from:http://blog.gosecure.ca/2016/04/27/binary-webshell-through-opcache-in-php-7/在 PHP 7.0 发布之初,就有不少 PHP 开发人员对其性能提升方面非常关注。在引入 OPcache 后,PHP的性能的确有了很大的提升,之后,很多开发人员都开始采用 OPcache 作为 P

from:http://blog.gosecure.ca/2016/04/27/binary-webshell-through-opcache-in-php-7/

在 PHP 7.0 发布之初,就有不少 PHP 开发人员对其性能提升方面非常关注。在引入 OPcache 后,PHP的性能的确有了很大的提升,之后,很多开发人员都开始采用 OPcache 作为 PHP 应用的加速器。OPcache 带来良好性能的同时也带来了新的安全隐患,下面的内容是 GoSecure 博客发表的一篇针对 PHP 7.0 的 OPcache 执行 PHP 代码的技术博文。

本文 会介绍一种新的在 PHP7 中利用默认的 OPcache 引擎实施攻击的方式。利用此攻击向量,攻击者可以绕过“Web 目录禁止文件读写”的限制 ,也可以执行他自己的恶意代码。

0x00 OPcache 利用方式简介

OPcache 是 PHP 7.0 中内建的缓存引擎。它通过编译 PHP 脚本文件为字节码,并将字节码放到内存中。

利用 PHP7 的 OPcache 执行 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/)里面的所有子目录以及文件都具有写权限。

#!shell
$ 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 的默认值为 1
opcache.file_cache_only = 1      ; PHP 7 的默认值为 0
opcache.file_cache = /tmp/opcache

此时,我们可以利用上传漏洞将文件上传到 Web 目录,但是发现 Web 目录没有读写权限。这个时候,就可以通过替换 /tmp/opcache/[system_id]/var/www/index.php.bin 为一个 webshell的二进制缓存文件运行 webshell。

利用 PHP7 的 OPcache 执行 PHP 代码

  1. 在本地创建 webshell 文件 index.php ,代码如下:

    #!php
    <?php 
       system($_GET['cmd']); 
    ?>
    
  2. 在 PHP.ini 文件中设置 opcache.file_cache 为你所想要指定的缓存目录

  3. 运行 PHP 服务器(php -S 127.0.0.1:8080) ,然后向 index.php 发送请求(wget 127.0.0.1:8080),触发缓存引擎进行文件缓存。

  4. 打开你所设置的缓存目录,index.php.bin 文件即为编译后的 webshell 二进制缓存文件。

  5. 修改 index.php.bin 文件头里的 system_id 为目标站点的 system_id 。在文件头里的签名部分的后面就是 system_id 的值。

    利用 PHP7 的 OPcache 执行 PHP 代码

  6. 通过上传漏洞将修改后的 index.php.bin 上传至 /tmp/opcache/[system_id]/var/www/index.php.bin ,覆盖掉原来的 index.php.bin

  7. 重新访问 index.php ,此时就运行了我们的 webshell

    利用 PHP7 的 OPcache 执行 PHP 代码

针对这种攻击方式,在 php.ini 至少有两种配置方式可以防御此类攻击。

  • 禁用 file_cache_only
  • 启用 validate_timestamp

0x02 绕过内存缓存(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 包的时候都是不会变的。

利用 PHP7 的 OPcache 执行 PHP 代码

注意观察上图,你会发现有些文件从2012年之后从没有被修改过,如:registration-functions.php 和 registration.php 。因此,这些文件在 WordPress 的多个版本中都是一样的。知道了时间戳,攻击者就可以绕过 validate_timestamps 限制,成功覆盖缓存文件,执行 webshell。二进制缓存文件的时间戳在 34字节偏移处。

利用 PHP7 的 OPcache 执行 PHP 代码

0x04 总结

OPcache 这种新的攻击向量提供了一些绕过限制的攻击方式。但是它并非一种通用的 PHP 漏洞。随着 PHP 7.0 的普及率不断提升,你将很有必要审计你的代码,避免出现上传漏洞。并且检查可能出现的危险配置项。

 
标签: PHP7
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • php7.2.4安装rabbitmq扩展的过程中错误处理
    1、根据自己php的版本选择 amqp包的版本,我开始随便选择了amqp-1.6.1.tar.gz make之后出现如下错误:/opt/amqp-1.6.1/amqp.c:984:37: error: too many arguments to function ‘zend_register_internal_class_ex’amqp_queue_exception_class_entry
    02-09
  • tp5+php7 实现发送邮件(qq邮箱和企业邮箱均验证)
    tp5+php7 实现发送邮件(qq邮箱和企业邮箱均验
    一:   准备工作              1:phpmailer核心文件下载地址:https://github.com/PHPMailer/PHPMailer               2、PHP扩展开启,openssl和sockets,打开php.ini文件,将这两个扩展钱的;号去掉。                    ①  
    02-09
  • 第二章:php7扩展开发[2] 怎么样创建类
    1.创建扩展进入${php-src}/ext目录,执行./ext_skel--extname=route,这时目录下会出现一个route的目录,cd ./route2.修改config.m4内容第十行左右,去掉dnlPHP_ARG_WITH(route, for route support,dnl Make sure that the comment is aligned:[ --with-route
    02-09
  • PHP之Trait详解 php7 trait
    PHP之Trait详解 php7 trait
    php从以前到现在一直都是单继承的语言,无法同时从两个基类中继承属性和方法,为了解决这个问题,php出了Trait这个特性用法:通过在类中使用use 关键字,声明要组合的Trait名称,具体的Trait的声明使用Trait关键词,Trait不能实例化 与普通类的异同:相同:t
    02-09
  • ubuntu14.04 安装apache+php7.2*
    1.安装apache2   sudo apt-get update  sudo apt-get install apache2  这时http://你机器的ip,就可以访问了   2.安装php7.0+及一些必要的扩展   apt-get install software-properties-common python-software-properties add-apt-repository p
    02-09
  • PHP7新功能及语法变化总结 PHP新特性
    1、标量类型声明有两种模式: 强制 (默认) 和 严格模式。 现在可以使用下列类型参数(无论用强制模式还是严格模式): 字符串(string), 整数 (int), 浮点数 (float), 以及布尔值 (bool)。2、返回值类型声明返回类型声明指明了函数返回值的类型。可用的类型与参
    02-09
  • php7修改时区无效如何解决
    这篇文章主要介绍“php7修改时区无效如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“php7修改时区无效如何解决”文章能帮助大家解决问题。php7修改时区无效的解决办法:1、从代码中动态修改时区,从而获
    02-08 php
  • PHP7框架之Lumen之Hello World。
    背景:听说Lumen起家是以一个叫av的框架,larvael。以Auth2.0起步,Lumen是精简版本,于是想了解一下。发现居然边TM路由就卡住了,Fuck,不光是我老外也有这个问题,都没有解决,主要是一个Nginx的转写问题,这块在Lumen的文档里写的很垃圾,我都不知这些人是
    02-05 PHP7
  • 亿级用户PC主站的PHP7升级实践
    亿级用户PC主站的PHP7升级实践
    伴随业务的增长,系统压力也在不断增加,再加上机房机架趋于饱和,无法更加有效应对各种突发事件。在这样的情况下,PC主站升级为PHP 7,有哪些技术细节可以分享?背景新浪微博在2016年Q2季度公布月活跃用户(MAU)较上年同期增长33%,至2.82亿;日活跃用户(D
    02-05 PHP7
  • PHP7 内核分析:变量的设计
    变量的结构变量的数据结构变量保存在zval的结构体中(与 PHP5 相同,但数据结构做了很大改变)。zval结构体定义在Zend/zend_types.h文件中,结构体如下:typedef struct _zval_struct zval;struct _zval_struct {zend_valuevalue;/* value */union {struct {Z
    02-05 PHP7
点击排行