WebLogic XMLDecoder反序列化漏洞

前言

这是我第一次接触java反序列化漏洞,之前也不会java,调试的过程比较艰辛,其中一些原理其实也没有太清楚,先整理一篇博客回顾一下再继续往下学习吧。CVE-2017-3506 & CVE-2017-10271漏洞产生的原因大致是Weblogic的WLS Security组件对外提供webservice服务,其中使用了XMLDecoder来解析用户传入的XML数据,在解析的过程中出现反序列化漏洞,导致可执行任意命令,攻击者发送精心构造的xml数据甚至能通过反弹shell拿到权限。

环境搭建

直接使用github上vulhub中的docker

https://github.com/vulhub/vulhub/blob/master/weblogic/CVE-2017-10271

之前我连docker都没用过,参考了这本书入门 Docker—从入门到实践

因为要动态调试,需要开启一个远程调试的端口8053,所以将docker-compose.yml文件改动如下

1
2
3
4
5
6
7
8
9
10
11
12
version: '2'

services:

weblogic:

image: vulhub/weblogic

ports:

- "7001:7001"
- "8453:8453"

执行命令 docker-compose up -d 完成搭建,若成功启动,打开浏览器访问http://127.0.0.1:7001应该会出现一个404页面

docker容器搭建完成后,使用命令docker exec -it weblogic /bin/bash 进入容器

修改/root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh 文件

找到如下代码段

添加图中的上面两行代码 原来并没有这两行代码

1
2
debugFlag="true"
export debugFlag

添加后运行一下这个脚本,退出并重启容器,这样就开启了debug模式,可以docker ps一下查看容器是否开启了8453端口。

tips:退出容器快捷键 Ctrl+D

因为要远程调试weblogic,而没有源码,所以要把容器中依赖的包拷出来

使用命令docker cp weblogic:/root/Oracle/Middleware/wlserver_10.3 ./WebLogic_jars

这是如果你使用的是windows系统会报错,因为windows对命令行下的路径长度有限制,所以要先进到docker里把要拷的文件夹打个包,再拉下来。

Linux 的打包命令为 zip -r zipname.zip targetdir

docker中好像没有zip命令,需要先apt-get install zip一下,

然后再执行zip -r wlserver_10.3.zip wlserver_10.3

这部分jar不全,还要将/root/Oracle/Middleware/modules 拷出来

IDEA远程调试

使用idea打开wlserver_10.3,将wlserver_10.3/server/lib和modules这两个文件夹添加到library

添加后,就会发现里面的.jar和.war的包都可以点开了,并且可以搜索里面的一些类和字符串了。

然后开始设置debug,点击Add Configuration...

添加一个Remote设置

端口设置为8453,并且设置Use module classpath

点击debug,出现如下字样,说明已经配置ok。

Connected to the target VM, address: 'localhost:8453', transport: 'socket'

在wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class中的handle方法下断点,看看能否击中断点。

以上,就搭建好了docker环境和调试环境,接下来就可以复现和动态调试了。

漏洞复现

因为对原理其实也是一知半解,所以先用师傅们的poc复现了一下

现在本地开一个监听端口

nc -l -p 8888

发送如下数据包

tips:/dev/tcp/ 后的ip换成自己的即可

反弹shell成功

查看burp中返回的xml数据,可以清晰的看到调用栈,调用栈在<ns2:frame />标签中

仔细分析weblogic返回的响应,我们可以大概定位到问题点,我们重点关注<ns2:frame />标签中class以weblogic开头的部分,这部分就是weblogic处理我们请求的调用栈逻辑,weblogic处理完后就到了XMLDecoder

这样,通过漏洞复现,我们得到了weblogic的处理流程如下(为了方便查看,调用栈顺序为从上到下)

  • weblogic.wsee.jaxws.workcontext.WorkContextServerTube->processRequest
  • weblogic.wsee.jaxws.workcontext.WorkContextTube->readHeaderOld
  • weblogic.wsee.jaxws.workcontext.WorkContextServerTube->receive
  • weblogic.workarea.WorkContextMapImpl->receiveRequest
  • weblogic.workarea.WorkContextLocalMap->receiveRequest
  • weblogic.workarea.spi.WorkContextEntryImpl->readEntry
  • weblogic.wsee.workarea.WorkContextXmlInputAdapter->readUTF

至此,漏洞复现已经完成,并根据漏洞复现中得到的信息,为动态调试打下了基础。

动态调试

从poc中可以看出,这个漏洞是wls-wsat这个接口出了问题,搜索一下文件,发现了wls-wsat.war这个包,打开后点击web.xml查看有哪些接口

进来后发现第一个接口就是我们想要的接口,调用栈和WebLogic servlet的分发机制比较复杂,我也不是很懂emm,只是跟着复现过程发现的函数调用栈跟了一遍流程。

具体流程如下

weblogic.wsee.jaxws.workcontext.WorkContextServerTube

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);
}

var1为POST传进来的XML数据,var3是xml的头部解析,如果存在(头不为空),就进入readHeaderOld()方法,跟进readHeaderOld()

weblogic.wsee.jaxws.workcontext.WorkContextTube

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);
}
}

第一步processRequest中我们只把头读了进来,其他的数据还在缓冲区中,使用ByteArrayOutputStream函数读取剩余数据到var4。经过一系列的处理后,如果没有问题就创建WorkContextXmlInputAdapter对象 var6,之后跟进receive()

weblogic.wsee.jaxws.workcontext.WorkContextServerTube

1
2
3
4
protected void receive(WorkContextInput var1) throws IOException {
WorkContextMapInterceptor var2 = WorkContextHelper.getWorkContextHelper().getInterceptor();
var2.receiveRequest(var1);
}

继续跟进receiveRequest()

weblogic.workarea.WorkContextMapImpl

1
2
3
public void receiveRequest(WorkContextInput var1) throws IOException {
((WorkContextMapInterceptor)this.getMap()).receiveRequest(var1);
}

将var1传到了receiveRequest()方法中,继续跟进

weblogic.workarea.WorkContextLocalMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void receiveRequest(WorkContextInput var1) throws IOException {
while(true) {
try {
WorkContextEntry var2 = WorkContextEntryImpl.readEntry(var1);
if (var2 == WorkContextEntry.NULL_CONTEXT) {
return;
}

String var3 = var2.getName();
this.map.put(var3, var2);
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest(" + var2.toString() + ")");
}
} catch (ClassNotFoundException var4) {
if (debugWorkContext.isDebugEnabled()) {
debugWorkContext.debug("receiveRequest : ", var4);
}
}
}
}

WorkContextEntryImpl.readEntry(var1);对传进来的数据进行处理,具体的代码好像看不太懂,但是跟进!readEntry()!

weblogic.workarea.spi.WorkContextEntryImpl

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));
}

在这里对var0执行了readUTF()方法,终于看到曙光了,最后一个方法,跟进

weblogic.wsee.workarea.WorkContextXmlInputAdapter

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

终于执行了readObject()方法,对XMLDecoder对象进行了反序列化,导致远程命令执行。

不用再跟进了,至此远程动态调试完成。

漏洞总结

WebLogic没有对XML的数据进行任何的过滤,导致可以构造XML数据,反序列化任意对象,从而RCE,这也就是CVE-2017-3506产生的原因。

这是我第一次对一个漏洞进行完整的复现和调试,其间遇到了许多问题,磕磕绊绊的终于完成了这篇博客,感谢很多师傅的帮助和博客。经过这次尝试,我对java反序列化和漏洞调试有了初步的认识,希望继续努力,早日完成下一篇博客!

Reference

Weblogic XMLDecoder RCE分析

WebLogic XMLDecoder反序列化漏洞(CVE-2017-10271)漏洞分析

从0开始学习WebLogic(Java)反序列化 (1)