xmldecoder反序列化漏洞分析

前言

java提供了很多xml文档解析的类库,包括dom4j,domj,SAX等库,可以进行xml文档的解析,这些库的使用不当,会导致XXE漏洞的发生,但是这些类库中,SAX库允许自己去定义整个xml文档处理的handler,可以在xml文档解析的过程中,对解析出来的节点进行一些操作

而为java反序列化定义的handler,会导致一些java反序列化的问题的发生

漏洞代码

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

public class xmlDecode {
    public static void xmldecode_unserilize(String path) throws Exception{
        File file = new File(path);
        FileInputStream fileInputStream = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fileInputStream);
        XMLDecoder xmlDecoder = new XMLDecoder(bis);
        xmlDecoder.readObject();
        xmlDecoder.close();
    }

    public static void main(String[] args){
        String path = "poc.xml";
        try{
            xmldecode_unserilize(path);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

poc:



    
        
            
                touch
            
            
                /tmp/xmlsuccess
            
        
        
    

xmldecoder漏洞分析

xml文档解析

image.png

获取到文件内容之后,用XMLDecoder解析,在下面调用了readObject方法

进入readObject方法中

image.png

这里对文档进行了解析,而XMLDecoder.this.handler其实就是
DocumentHandler


image.png

这个handler很重要,所有反序列化的操作都是在这个类中进行的

image.png

之类通过SAXParserFactory实例化了一个SAXParser的实例,并且调用了其中的parse方法

复现过java XXE漏洞的师傅应该看见过SAXParser的用法,它允许用户自己去定义处理文档的handler

image.png

可以看到,用户可以将自己定义的Handler,只要这个类继承了DefaultHandler,可以看一下官网的例子:

public class SaxHandler extends DefaultHandler {

    public void startDocument() throws SAXException {
        System.out.println("start document   : ");
    }

    public void endDocument() throws SAXException {
        System.out.println("end document     : ");
    }

    public void startElement(String uri, String localName,
        String qName, Attributes attributes)
    throws SAXException {

        System.out.println("start element    : " + qName);
    }

    public void endElement(String uri, String localName, String qName)
    throws SAXException {
        System.out.println("end element      : " + qName);
    }

    public void characters(char ch[], int start, int length)
    throws SAXException {
        System.out.println("start characters : " +
            new String(ch, start, length));
    }
}

自己的Handler可以在解析的不同阶段,进行不同的操作,这个特点就让xml文档成为了可以进行java序列化的载体,DocumentHandler这个handler就是处理xml文档反序列化的

接下来,调用SAXParser对xml文档进行解析,在对一些属性的设置以后,真正的解析流程在XML11Configuration这个类中的parse方法中开始

首先是对实体的解析:


因为我们的文档中并没有xml实体,所以这一步不用关注

之后进行文档的解析

image.png

进入到scanDocument函数中

image.png

这里通过next函数,解析了文档,并且返回当前解析的状态,整个文档的具体解析过程在XMLDocumentFragmentScannerImpl这个类的ContentDriver类中,里面的解析过程很复杂,具体的解析过程不要过于关注,重点在解析出来的结果的处理上

image.png
image.png

当发现当前的文档为根节点的时候,调用fContentDriver的next方法,在这里进行ROOT节点的解析

image.png

一直到XMLDocumentFragmentScannerImpl的scanStartElement方法中

image.png

在之类对文档进行解析,调用scanName解析出来第一个节点名为:java

之后寻找java节点的结束字符在哪,并且解析出来中间的所有属性

image.png

在解析结束以后,会将这个节点中的所有属性添加到fAttributes中

image.png

最后来到startElement函数中,可以认为fElementQName就是我们的节点名称,fAttributes就是这个节点中所有属性组成的一个字典

image.png

最后调用到DocumentHandler的startElement才到真正反序列化的地方

image.png

到这里,一个节点的解析就算结束了,头都看大了,先附上一张调用栈,再到DocumentHandler中看具体的反序列化过程

startElement:509, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
startElement:745, XMLDTDValidator (com.sun.org.apache.xerces.internal.impl.dtd)
scanStartElement:1363, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanRootElementHook:1292, XMLDocumentScannerImpl$ContentDriver (com.sun.org.apache.xerces.internal.impl)
next:3138, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:880, XMLDocumentScannerImpl$PrologDriver (com.sun.org.apache.xerces.internal.impl)
next:606, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:510, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:848, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:777, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:648, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:332, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
run:375, DocumentHandler$1 (com.sun.beans.decoder)
run:372, DocumentHandler$1 (com.sun.beans.decoder)
doPrivileged:-1, AccessController (java.security)
doIntersectionPrivilege:76, ProtectionDomain$1 (java.security)
parse:372, DocumentHandler (com.sun.beans.decoder)
run:201, XMLDecoder$1 (java.beans)
run:199, XMLDecoder$1 (java.beans)
doPrivileged:-1, AccessController (java.security)
parsingComplete:199, XMLDecoder (java.beans)
readObject:250, XMLDecoder (java.beans)
xmldecode_unserilize:12, xmlDecode
main:19, xmlDecode

节点的反序列化

java节点

在最后,调用了DocumentHandler.startElement函数,我们进入看一下

image.png

在这里会根据不同的节点实例化不同的节点Handler

image.png

this.handlers中寻找java节点对应的handler,可以看一下this.handlers里面所有的handler都是什么,它是一个HashMap,在属性中有定义private final Map> handlers = new HashMap();

在构造方法中,对handlers进行了赋值

image.png

这也就是XMLDecoder所有支持的节点类型,不同的节点会调用不同的ElementHandler进行处理,我们的java节点,应该是被JavaElementHandler处理的

回到startElement的方法中

try {
    this.handler = (ElementHandler)this.getElementHandler(var3).newInstance();
    this.handler.setOwner(this);
    this.handler.setParent(var5);
} catch (Exception var10) {
    throw new SAXException(var10);
}

在实例化JavaElementHandler类以后,调用了setParent将上一次的handler保存,这一步相当于将每一个节点的处理类串成了一个链

image.png

这一步获取到所有的节点属性,并且调用处理handler自己实现的addAttribute方法,可以看一下JavaElementHandler的addAttribute

image.png

这里通过我们设置的class属性的内容,获取了对应的类,也就是java.beans.XMLDecoder类的class

之后调用对应handler的startElement,而java的handler没有操作,所以进行下一个节点的解析

object节点

下一个节点是object,具体解析流程就不再分析

object对应的Handler为ObjectElementHandler,可以发现,startElement中的重点其实就是每个Handler的addAttribute方法和startElement方法

image.png

调用父类的addAttribute方法

image.png

这一步获得了ProcessBuilder的class

image.png

array节点

处理handler:ArrayElementHandler
addAttribute:


image.png
image.png

这里定义了数组元素的类型和数组大小

startElement:


image.png
image.png

这里实例化了一个数组元素,并且返回了一个ValueObject对象

void节点

void节点比较特殊,void节点其实是object节点的一个子类,它本身没有定义什么方法

image.png
image.png

我们可以不用过多的关注void节点的处理规则,只需要理解它的作用就是声明一个变量就可以,可以这么理解:

String name = 'flight';
等价于

flight

节点处理结束

在一个节点处理结束以后,会由内向外依次调用每个节点的endElement方法

比如我们的poc


    
        
            
                touch
            
            
                /tmp/xmlsuccess
            
        
        
    


1. string -> end # touch
2. void -> end # index 0
3. string -> end # /tmp/xmlsuccess
4. void ->end # index 1
5. array -> end
6. void -> end # method start
7. object -> end
8. java -> end

在解析完touch的string节点的之后,触发string的endElement,主要就是将将值设置为StringElementHandler的属性

image.png

之后向ArrayElementHandler中的数组添加进去这个String,对每个element都会调用getValueObject方法,而其中

void节点的endElement很有意思


image.png

看一下getContextBean方法


image.png

这里会获取上一个Handler的ValueObject的值,而这个ValueObject的value就是我们在属性中定义的对象,void节点的上一个节点是array节点,我们在array节点中定义了一个大小为2的String数组,所以获取到的ValueObject就为这个数组


image.png
image.png

因为我们只设置了void的属性为index="0",所以会进入到var4=set的条件中

最后的Express类,相当于是对var3这个对象调用其中的var4的方法,参数为var2,这样,就为这个String数组赋值了第一个值为touch

来到第二个void标签

当来到

image.png

进入到getContextBean

image.png

在这里调用了parent的getValueObject方法,也就是object标签

image.png

获取了ObjectElementHandler中的ProcessBuilder对象

最后调用start执行命令

你可能感兴趣的:(xmldecoder反序列化漏洞分析)