漏洞概要
Jenkins 未授权远程代码执行漏洞, 允许攻击者将序列化的Java SignedObject对象传输给Jenkins CLI处理,反序列化ObjectInputStream作为Command对象,这将绕过基于黑名单的保护机制, 导致代码执行。
漏洞触发执行流程
SSD的报告披露了完整的漏洞细节,作为才学JAVA的我来说,看完这份报告,依旧不清楚具体的执行流程,因此有了下文,梳理漏洞触发的具体执行流程。
触发jenkins反序列化导致代码执行的漏洞发生在使用HTTP协议实现双向通信通道的代码中,Jenkins利用此通道来接收命令。大致流程如下图:

如何建立双向Channel
基于HTTP建立双向Channel的入口函数位于jenkins-2.46.1/core/src/main/java/hudson/cli/CLIAction.java
文件中
1 | "cli") ( |
从上述代码可知,建立一对双向通道(download/upload), 需要发送两次POST请求,根据请求头Session字段的值uuid识别不同的双向通道,Side字段的值识别download或upload通道,请求发送的顺序是先发送download请求再发送upload请求,跟进download
函数(/Users/js/IdeaProjects/vulnhub/jenkins-2.46.1/core/src/main/java/hudson/model/FullDuplexHttpChannel.java
), 当服务器收到download请求时会阻塞请求,等待upload请求,收到upload请求后,新建Channel对象处理upload请求和返回响应,代码如下:
1 | public synchronized void download(StaplerRequest req, StaplerResponse rsp) throws InterruptedException, IOException { |
以上就是建立双向通道的基本过程。
Channel对象启动ReaderThread
upload请求作为输入流实例化Channel对象(~/.m2/repository/org/jenkins-ci/main/remoting/3.7/remoting-3.7-sources.jar!/hudson/remoting/Channel.java
), Channel类的构造链比较繁琐如下图,
最终调用的构造方法为Channel(ChannelBuilder settings, CommandTransport transport)
, 该构造方法的transport参数,由ChannelBuilder类的negotiate()方法获得。
1 | protected CommandTransport negotiate(final InputStream is, final OutputStream os) throws IOException { |
negotiate()会检查输入(upload请求)的前导码, 所有发往Jenkins CLI的命令中都包含某种格式的前导码(preamble),前导码格式通常为:<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4=
, 该前导码包含一个经过base64编码的序列化对象。“Capability”类型的序列化对象的功能是告诉服务器客户端具备哪些具体功能(比如HTTP分块编码功能)。
最后调用makeTransport()方法返回CommandTransport
对象, 根据cap是否支持Chunking
返回不同的对象ChunkedCommandTransport
或ClassicCommandTransport
。
1 | protected CommandTransport makeTransport(InputStream is, OutputStream os, Mode mode, Capability cap) throws IOException { |
利用SSD的PoC脚本发送的upload请求返回的是ClassicCommandTransport
对象,其继承关系如下图所示。

Channel构造函数Channel(ChannelBuilder settings, CommandTransport transport)
中, transport.setup()调用SynchronousCommandTransport类的setup()方法来启动一个ReaderThread线程。
1 | public void setup(Channel channel, CommandReceiver receiver) { |
读取Command对象
通过上面的ReaderThread.start()方法启动一个线程,ReaderThread为SynchronousCommandTransport类的内部类,在run()方法中,调用ClassicCommandTransport
类的read()方法读取输入,read()方法实际是调用Command类的readFrom()方法读取,通过反序列化输入返回一个Command对象。
1 | private final class ReaderThread extends Thread { |
1 | public final Command read() throws IOException, ClassNotFoundException { |

在反序列化输入返回一个Command对象时就执行了cmd命令,而不是通过正常的回调handle()方法执行cmd命令,反序列化导致的执行代码触发的相关异常如下:

类型转换异常ClassCastException
: org.apache.commons.collections.map.ReferenceMap cannot be cast to hudson.remoting.Command
.
正常执行Command
虽说反序列化时就执行了cmd代码,这里也顺带了解下正常的执行cmd的过程。SynchronousCommandTransport类的run()方法中,获得返回的Command对象(cmd),然后调用receiver.handle(cmd);
处理命令,其实质是回调Channel类构造方法里面的handle方法,而传入handle方法的cmd参数就是反序列化得到的Command对象。
1 | transport.setup(this, new CommandReceiver() { |
绕过黑名单保护机制
上面过程主要讲述的是漏洞触发的流程,而该漏洞的核心是反序列化Java SignedObject对象会绕过黑名单保护机制,从而导致的代码执行漏洞。

ClassFilter类定义的默认的黑名单如下:
1 | private static final String[] DEFAULT_PATTERNS = { |
黑名单机制绕过可以通过分析补丁得到印证。
参考
- http://www.securityfocus.com/bid/98056
- https://blogs.securiteam.com/index.php/archives/3171
- https://jenkins.io/security/advisory/2017-04-26/
- https://github.com/jenkinsci/jenkins/commit/36b8285a41eb28333549e8d851f81fd80a184076
- https://github.com/jenkinsci/jenkins/commit/f237601afd750a0eaaf961e8120b08de238f2c3f
- http://www.lilihongblog.com/Blog/jenkins+Slave+Receiving+Remote+Request