栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

XMLDecoder解析流程分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

XMLDecoder解析流程分析

XMLDecoder解析流程分析

最开始是准备入手CVE-2018-10271的,结果百度搜了很多文章都是到MXLDecoder.readObject()就结束了。而我在此之前并没有接触过XMLDecoder,所以只能回头补课了。

首先写一个Demo进行调试

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

public class XMLDecoderDemo01 {

    public static void XMLDecode_Deserialize(String path) throws Exception {
        File file = new File(path);
        FileInputStream fis = new FileInputStream(file);
        BufferedInputStream bis = new BufferedInputStream(fis);
        XMLDecoder xd = new XMLDecoder(bis);
        xd.readObject(); // 在这打断点
        xd.close();
    }


    public static void main(String[] args){
        //XMLDecode Deserialize Test
        String path = System.getProperty("user.dir") + "\src\pocDemo01.xml";

        try {
            
            XMLDecode_Deserialize(path);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然后就是xml文档了。

pocDemo01.xml


    
        
            
                calc
            
        
        
    

非常简单的一个xml文档,通过ProcessBuilder启动计算器calc。

过程分析

进入debug模式,首先进入眼帘的是DocumentHandler.parse()方法,可以通过调用栈找到。

//DocumentHandler.java
public void parse(final InputSource var1) {
    if (this.acc == null && null != System.getSecurityManager()) {
        throw new SecurityException("AccessControlContext is not set");
    } else {
        AccessControlContext var2 = AccessController.getContext();
        SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction() {
            public Void run() {
                try {
           SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this);
                } catch (ParserConfigurationException var3) {
                    DocumentHandler.this.handleException(var3);
                } catch (SAXException var4) {
                    Object var2 = var4.getException();
                    if (var2 == null) {
                        var2 = var4;
                    }

                    DocumentHandler.this.handleException((Exception)var2);
                } catch (IOException var5) {
                    DocumentHandler.this.handleException(var5);
                }

                return null;
            }
        }, var2, this.acc);
    }
}

在doIntersectionPrivilege()方法里的参数action是一个匿名类,而这个匿名类的run()方法会实例化SAXParser并调用其parse()方法,这是我们下一步的入口函数。

//SAXParserImpl.java
public void parse(InputSource is, DefaultHandler dh)
    throws SAXException, IOException {
    if (is == null) {
        throw new IllegalArgumentException();
    }
    if (dh != null) {
        xmlReader.setContentHandler(dh);
        xmlReader.setEntityResolver(dh);
        xmlReader.setErrorHandler(dh);
        xmlReader.setDTDHandler(dh);
        xmlReader.setDocumentHandler(null);
    }
    xmlReader.parse(is);
}

这里的xmlReader是SAXParserImpl的一个内部类JAXPSAXParser。

//SAXParserImpl$JAXPSAXParser
public void parse(InputSource inputSource)
    throws SAXException, IOException {
    if (fSAXParser != null && fSAXParser.fSchemaValidator != null) {
        if (fSAXParser.fSchemaValidationManager != null) {
            fSAXParser.fSchemaValidationManager.reset();
            fSAXParser.fUnparsedEntityHandler.reset();
        }
        resetSchemaValidator();
    }
    super.parse(inputSource);
}

在之后会经过一系列的parse()方法,这些parse()方法主要是为了以后的解析过程做准备,一直到XML11Configuration.parse()方法才开始进行解析。

//XML11Configuration.java
public boolean parse(boolean complete) throws XNIException, IOException{
    
    
    
    try {
            return fCurrentScanner.scanDocument(complete);
        } catch (XNIException ex) {}
    
}

顾名思义scanDocument()方法就是扫描我们的pocDemo01.xml文档的。

//XMLDocumentFragmentScannerImpl.java

public boolean scanDocument(boolean complete)
throws IOException, XNIException {

    // keep dispatching "events"
    fEntityManager.setEntityHandler(this);
    //System.out.println(" get Document Handler in NSDocumentHandler " + fDocumentHandler );

    int event = next();
    do {
        switch (event) {
            case XMLStreamConstants.START_DOCUMENT :
                break;
            case XMLStreamConstants.ATTRIBUTE :
                break;
            case XMLStreamConstants.END_ELEMENT :
                //do not give callback here.
                //this callback is given in scanEndElement function.
                //fDocumentHandler.endElement(getElementQName(),null);
                break;
            default :
                throw new InternalError("processing event: " + event);
        }
        event = next();
    } while (event!=XMLStreamConstants.END_DOCUMENT && complete);

    if(event == XMLStreamConstants.END_DOCUMENT) {
        fDocumentHandler.endDocument(null);
        return false;
    }

    return true;

} // scanDocument(boolean):boolean

这里一些老的文章在分析XMLStreamConstants.END_ELEMENT,但是可以看到已经被注释掉了。根据注释原来的处理方法被移动到scanEndElement function,在IDEA搜索scanEndElement函数,通过IDEA的usage功能,找到在next()方法里对该函数进行了调用,在scanEndElement()方法处打上断点。

来看一下next()的注释


public int next() throws IOException, XNIException{}

得知该函数就是解析XML文档的函数。因为其过程过于复杂,详细可看XMLDecoder 解析流程(详细讲解)。

简单来说,next()中每次解析出的结果都会有对应的Handler进行处理。

XML文档的解析是从里到外解析的,在本例中pocDemo01.xml就是先解析calc,往外一层的 ,以此类推,我们直接看到的解析部分

//XMLDocumentFragmentScannerImpl$FragmentContentDriver
public int next() throws IOException, XNIException{
    switch(fScannerState){

        case XMLEvent.START_DOCUMENT :
            return XMLEvent.START_DOCUMENT;

        case SCANNER_STATE_START_ELEMENT_TAG :{

            //xxx this function returns true when element is empty.. can be linked to end element event.
            //returns true if the element is empty
            fEmptyElement = scanStartElement() ;
            //if the element is empty the next event is "end element"
            if(fEmptyElement){
                setScannerState(SCANNER_STATE_END_ELEMENT_TAG);
            }else{
                //set the next possible state
                setScannerState(SCANNER_STATE_CONTENT);
            }
            return XMLEvent.START_ELEMENT ;
        }
    }
}

从scanStartElement()方法进入

//XMLDocumentFragmentScannerImpl.java
protected boolean scanStartElement()
    throws IOException, XNIException {
    // call handler
    if (fDocumentHandler != null) {
        fDocumentHandler.emptyElement(fElementQName, fAttributes, null);
    }
}

//AbstractXMLDocumentParser.java
public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs)
        throws XNIException {

        startElement(element, attributes, augs);
        endElement(element, augs);

    } 

这里fDocumentHandler是SAXParserImpl$JAXPSAXParser,因继承关系会调用父类AbstractXMLDocumentParser.emptyElement()方法。

不过在AbstractXMLDocumentParser中并没有endElement()方法的具体实现,因此会从继承关系向下一层寻找endElement()方法,而找到的这个类就是AbstractSAXParser。

//AbstractSAXParser.java

public void endElement(QName element, Augmentations augs) throws XNIException {

    try {
        // SAX2
        if (fContentHandler != null) {
            fAugmentations = augs;
            String uri = element.uri != null ? element.uri : "";
            String localpart = fNamespaces ? element.localpart : "";
            fContentHandler.endElement(uri, localpart, element.rawname);
        }
    }
    catch (SAXException e) {
        throw new XNIException(e);
    }
}

从注释得知endElement()方法将会结束一个Element的解析,这里的fContentHandler是DocumentHandler,跟进其endElement()方法。

//DocumentHandler.java
public void endElement(String var1, String var2, String var3) {
    try {
        this.handler.endElement();
    } catch (RuntimeException var8) {
        this.handleException(var8);
    } finally {
        this.handler = this.handler.getParent();
    }

}

DocumentHandler是一个关键类。

DocumentHandler继承自DefaultHandler,DefaultHandler是使用SAX进行XML解析的默认Handler,所以Weblogic在对XML对象进行validate的时候也使用了SAX,保证过程的一致性。

DefaultHandler实现了EntityResolver, DTDHandler, ContentHandler, ErrorHandler四个接口。

DocumentHandler主要改写了ContentHandler中的几个接口,毕竟主要是针对内容进行解析的,其它的保留默认就好。

这里的this.handler是VoidElementHandler,但是VoidElementHandler并没有实现endElement()方法,所以向上父类寻找endElement()方法。

package com.sun.beans.decoder;

final class VoidElementHandler extends ObjectElementHandler {
    VoidElementHandler() {
    }

    protected boolean isArgument() {
        return false;
    }
}

向上找的父类ObjectElementHandler也没有实现endElement()方法,所以继续向上找ObjectElementHandler的父类NewElementHandler类,NewElementHandler也没有最后在所有Element的基类ElementHandler找到了方法实现。

//ElementHandler.java
public abstract class ElementHandler {
    public void endElement() {
        ValueObject var1 = this.getValueObject();
        if (!var1.isVoid()) {
            if (this.id != null) {
                this.owner.setVariable(this.id, var1.getValue());
            }

            if (this.isArgument()) {
                if (this.parent != null) {
                    this.parent.addArgument(var1.getValue());
                } else {
                    this.owner.addObject(var1.getValue());
                }
            }
        }
    }

    protected abstract ValueObject getValueObject();
}

然而紧接着的this.getValueObject()却又得从VoidElementHandler向上找了,因为ElementHandler没有getValueObject()的具体实现。

根据Java的规则

java子类调用父类的方法:

  1. 子类的对象调用方法时,会首先在子类中查找,如果子类中没有该方法,再到父类中查找;
  2. 如果该方法中又调用了其他方法,那么还是按照之前的顺序,先在子类中查找,再在父类中查找。

先找VoidElementHandler的父类ObjectElementHandler,然而并没有getValueObject()的实现,继续向上在NewElementHandler里找到了。

//NewElementHandler.java
protected final ValueObject getValueObject() {
    if (this.arguments != null) {
        try {
            this.value = this.getValueObject(this.type, this.arguments.toArray());
        } catch (Exception var5) {
            this.getOwner().handleException(var5);
        } finally {
            this.arguments = null;
        }
    }

    return this.value;
}

结果立马又是一个getValueObject(this.type, this.arguments.toArray()),又从VoidElementHandler向上找了,这回直接在ObjectElementHandler里找到了getValueObject(Class var1, Object[] var2)的实现。

这个父类方法调用链感觉还是有点复杂的。

//ObjectElementHandler.java
protected final ValueObject getValueObject(Class var1, Object[] var2) throws Exception {
    if (this.field != null) {
        return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this.getContextBean(), this.field));
    } else if (this.idref != null) {
        return ValueObjectImpl.create(this.getVariable(this.idref));
    } else {
        Object var3 = this.getContextBean();
    }
        String var4;
        var4 = this.method != null && 0 < this.method.length() ? this.method : "new";

        Expression var5 = new Expression(var3, var4, var2);
        return ValueObjectImpl.create(var5.getValue());
    }
}

通过变量窗口可知通过var3=ProcessBuilder、var4="start"、var2=Object[0]得到了一个Expression,这个Expression通过指定的target对象调用其methodName方法,并传参arguments。在这里分别依次对应var3、var4、var2。

@ConstructorProperties({"target", "methodName", "arguments"})
public Expression(Object target, String methodName, Object[] arguments) {
    super(target, methodName, arguments);
}

最后通过Expression启动start()方法。

参考文章:

  • XMLDecoder解析流程分析 这个主要是弄清楚ElementHandler和其一堆子类和各种继承关系。
  • (Java)XMLDecoder反序列化漏洞 简单分析了流程。
  • XMLDecoder 解析流程(详细讲解) 就如标题所说,非常的详细。
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号