从代码层面分析Jenkins未授权访问CVE-2017-1000353
字数 1318 2025-08-24 16:48:16

Jenkins未授权访问漏洞CVE-2017-1000353深度分析

漏洞概述

CVE-2017-1000353是Jenkins CI工具中的一个远程代码执行漏洞,该漏洞存在于Jenkins利用HTTP协议进行双向通信的过程中,通过反序列化攻击实现远程代码执行。

环境搭建

  1. 下载Jenkins 2.190.3版本:

    wget https://repo.huaweicloud.com/jenkins/redhat-stable/jenkins-2.190.3-1.1.noarch.rpm
    
  2. 安装Jenkins:

    rpm -ivh jenkins-2.190.3-1.1.noarch.rpm
    
  3. 启动服务:

    systemctl start jenkins
    systemctl status jenkins
    
  4. 关闭防火墙(测试环境)

漏洞原理分析

1. 双向通道建立机制

漏洞的核心在于Jenkins CLI的双向通信机制,主要代码位于hudson.cli.CLIAction类中:

@Extension
@Symbol("cli")
@Restricted(NoExternalUse.class)
public class CLIAction implements UnprotectedRootAction, StaplerProxy {
    private transient final Map<UUID, FullDuplexHttpChannel> duplexChannels = new HashMap<UUID, FullDuplexHttpChannel>();
    
    @Override
    public Object getTarget() {
        StaplerRequest req = Stapler.getCurrentRequest();
        if (req.getRestOfPath().length()==0 && "POST".equals(req.getMethod())) {
            throw new CliEndpointResponse();
        } else {
            return this;
        }
    }
    
    private class CliEndpointResponse extends HttpResponseException {
        @Override
        public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
            try {
                UUID uuid = UUID.fromString(req.getHeader("Session"));
                rsp.setHeader("Hudson-Duplex", "");
                FullDuplexHttpChannel server;
                if (req.getHeader("Side").equals("download")) {
                    duplexChannels.put(uuid, server = new FullDuplexHttpChannel(uuid, !Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
                        @Override
                        protected void main(Channel channel) throws IOException, InterruptedException {
                            channel.setProperty(CLICommand.TRANSPORT_AUTHENTICATION, Jenkins.getAuthentication());
                            channel.setProperty(CliEntryPoint.class.getName(), new CliManagerImpl(channel));
                        }
                    });
                    try {
                        server.download(req, rsp);
                    } finally {
                        duplexChannels.remove(uuid);
                    }
                } else {
                    duplexChannels.get(uuid).upload(req, rsp);
                }
            } catch (InterruptedException e) {
                throw new IOException(e);
            }
        }
    }
}

关键点:

  • 通过SessionSide请求头控制通道建立
  • Side: download时创建新通道并放入duplexChannels映射
  • Side: upload时从duplexChannels获取已有通道

2. 通道数据传输过程

FullDuplexHttpChannel类负责数据传输:

public synchronized void download(StaplerRequest req, StaplerResponse rsp) throws InterruptedException, IOException {
    // 等待上传通道建立
    long end = System.currentTimeMillis() + CONNECTION_TIMEOUT;
    while (upload == null && System.currentTimeMillis() < end)
        wait(1000);
    if (upload == null)
        throw new IOException("HTTP full-duplex channel timeout: " + uuid);
    
    try {
        channel = new Channel("HTTP full-duplex channel " + uuid, 
                            Computer.threadPoolForRemoting, 
                            Mode.BINARY, 
                            upload, out, null, restricted);
    } finally {
        completed = true;
        notify();
    }
}

public synchronized void upload(StaplerRequest req, StaplerResponse rsp) throws InterruptedException, IOException {
    rsp.setStatus(HttpServletResponse.SC_OK);
    InputStream in = req.getInputStream();
    if (DIY_CHUNKING) in = new ChunkedInputStream(in);
    
    upload = in;
    notify();
    
    while (!completed)
        wait();
}

关键点:

  • download()等待upload通道建立,超时时间为CONNECTION_TIMEOUT
  • 创建Channel对象时传入upload输入流
  • upload()设置输入流并通知download()线程

3. 命令传输与反序列化

ChannelBuilder.negotiate()方法处理命令传输:

protected CommandTransport negotiate(final InputStream is, final OutputStream os) throws IOException {
    Mode[] modes = {Mode.BINARY, Mode.TEXT};
    byte[][] preambles = new byte[][]{Mode.BINARY.preamble, Mode.TEXT.preamble, Capability.PREAMBLE};
    int[] ptr = new int[3];
    Capability cap = new Capability(0);
    
    while (true) {
        int ch = is.read();
        for (int i = 0; i < preambles.length; i++) {
            byte[] preamble = preambles[i];
            if (preamble[ptr[i]] == ch) {
                if (++ptr[i] == preamble.length) {
                    switch (i) {
                        case 0: case 1:
                            return makeTransport(is, os, mode, cap);
                        case 2:
                            cap = Capability.read(is);
                    }
                }
            }
        }
    }
}

关键点:

  • 检查输入流的前导码(preamble)
  • 前导码格式示例:<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4=
  • 前导码包含base64编码的序列化Capability对象

makeTransport()方法创建传输对象:

protected CommandTransport makeTransport(InputStream is, OutputStream os, Mode mode, Capability cap) throws IOException {
    FlightRecorderInputStream fis = new FlightRecorderInputStream(is);
    if (cap.supportsChunking())
        return new ChunkedCommandTransport(cap, mode.wrap(fis), mode.wrap(os), os);
    else {
        ObjectOutputStream oos = new ObjectOutputStream(mode.wrap(os));
        oos.flush();
        return new ClassicCommandTransport(
            new ObjectInputStreamEx(mode.wrap(fis), getBaseLoader(), getClassFilter()),
            oos, fis, os, cap);
    }
}

4. 命令执行流程

  1. Channel构造函数调用transport.setup()启动ReaderThread线程
  2. ReaderThread调用read()方法读取命令
  3. Command.readFrom()反序列化输入流生成Command对象
  4. 反序列化过程中执行恶意代码

关键代码:

private final class ReaderThread extends Thread {
    public ReaderThread(CommandReceiver receiver) {
        super("Channel reader thread: " + channel.getName());
        this.receiver = receiver;
    }
    
    @Override
    public void run() {
        try {
            while (!channel.isInClosed()) {
                Command cmd = null;
                try {
                    cmd = read();
                    // 正常情况下应调用receiver.handle(cmd)
                } catch (Throwable t) {
                    // 错误处理
                }
            }
        } finally {
            // 清理代码
        }
    }
}

public final Command read() throws IOException, ClassNotFoundException {
    try {
        Command cmd = Command.readFrom(channel, ois);
        return cmd;
    } catch (RuntimeException e) {
        throw new IOException("Failed to read the response", e);
    }
}

漏洞利用关键点

  1. 前导码要求:攻击数据必须包含特定的前导码才能被正确处理
  2. 反序列化点Command.readFrom()方法会反序列化输入数据
  3. 执行时机:反序列化过程中就会执行恶意代码,而非正常的handle()方法调用
  4. 认证绕过:CLI连接建立时不需要任何权限验证

防御措施

  1. 升级到修复版本
  2. 禁用Jenkins CLI(通过管理界面或修改配置文件)
  3. 实施网络访问控制,限制Jenkins管理端口访问
  4. 使用反向代理并配置适当的访问控制

总结

CVE-2017-1000353漏洞的核心在于Jenkins CLI的双向通信机制中缺乏对反序列化操作的适当安全控制。攻击者可以通过构造恶意的序列化数据,在无需认证的情况下实现远程代码执行。理解这一漏洞需要深入分析Jenkins的通信协议实现、反序列化机制以及线程处理流程。

Jenkins未授权访问漏洞CVE-2017-1000353深度分析 漏洞概述 CVE-2017-1000353是Jenkins CI工具中的一个远程代码执行漏洞,该漏洞存在于Jenkins利用HTTP协议进行双向通信的过程中,通过反序列化攻击实现远程代码执行。 环境搭建 下载Jenkins 2.190.3版本: 安装Jenkins: 启动服务: 关闭防火墙(测试环境) 漏洞原理分析 1. 双向通道建立机制 漏洞的核心在于Jenkins CLI的双向通信机制,主要代码位于 hudson.cli.CLIAction 类中: 关键点: 通过 Session 和 Side 请求头控制通道建立 Side: download 时创建新通道并放入 duplexChannels 映射 Side: upload 时从 duplexChannels 获取已有通道 2. 通道数据传输过程 FullDuplexHttpChannel 类负责数据传输: 关键点: download() 等待 upload 通道建立,超时时间为 CONNECTION_TIMEOUT 创建 Channel 对象时传入 upload 输入流 upload() 设置输入流并通知 download() 线程 3. 命令传输与反序列化 ChannelBuilder.negotiate() 方法处理命令传输: 关键点: 检查输入流的前导码(preamble) 前导码格式示例: <===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4= 前导码包含base64编码的序列化 Capability 对象 makeTransport() 方法创建传输对象: 4. 命令执行流程 Channel 构造函数调用 transport.setup() 启动 ReaderThread 线程 ReaderThread 调用 read() 方法读取命令 Command.readFrom() 反序列化输入流生成 Command 对象 反序列化过程中执行恶意代码 关键代码: 漏洞利用关键点 前导码要求 :攻击数据必须包含特定的前导码才能被正确处理 反序列化点 : Command.readFrom() 方法会反序列化输入数据 执行时机 :反序列化过程中就会执行恶意代码,而非正常的 handle() 方法调用 认证绕过 :CLI连接建立时不需要任何权限验证 防御措施 升级到修复版本 禁用Jenkins CLI(通过管理界面或修改配置文件) 实施网络访问控制,限制Jenkins管理端口访问 使用反向代理并配置适当的访问控制 总结 CVE-2017-1000353漏洞的核心在于Jenkins CLI的双向通信机制中缺乏对反序列化操作的适当安全控制。攻击者可以通过构造恶意的序列化数据,在无需认证的情况下实现远程代码执行。理解这一漏洞需要深入分析Jenkins的通信协议实现、反序列化机制以及线程处理流程。