Java XMLDecoder反序列化分析
简介
XMLDecoder是一套用于对XML进行序列化或反序列化的一套API,它在JDK1.4就已经被开发了出来,它对XML的解析模式并不是更为人所知的DOM解析,而是SAX解析 DOM解析在解析XML时会读取所有数据然后生成DOM树来解析,而SAX则是线性读取XML,所以SAX解析XML性能消耗相对较小
两者区别如下:
在Weblogic中由于was-wast
、wls9_async
等多个组件使用了XMLDecoder来进行解析xml文档,导致了攻击者可以构造恶意的XML文档来触发反序列化去执行命令,修复方式为采用黑名单禁用object等标签,本文使用jdk7u21去分析XMLDecoder是如何进行操作的
反序列化分析
测试代码如下:
|
|
直接跟进到xmlDecoder.readObject()
中,这里的readObject()
并非原生的反序列化readObject()
只是同名而已
|
|
继续跟进到java.beans.XMLDecoder#parsingComplete
中,通过XMLDecoder.this.handler.parse()
去解析XMLDecoder.this.input
,而input就是封装过的传进去的xml
在com.sun.beans.decoder.DocumentHandler#parse
中通过SAX的工厂模式创建实例并且调用SAX解析,而其中DocumentHandler.this
是xml对应标签进行解析处理的Handler
继续跟进到com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl#parse
中,先进行设置Handler
然后继续解析
进入到重载的SAXParserImpl#parse
中继续跟进
在AbstractSAXParser#parse
中,将inputSource
的值赋值给xmlInputSource
并继续解析
在XMLParser#parse
中,然后使用XML11Configuration#parse
继续对其解析
继续往下走然后在XML11Configuration#parse(boolean)
中进入到fCurrentScanner.scanDocument()
中
在XMLDocumentFragmentScannerImpl#scanDocument
中通过do while
循环对xml文档进行遍历解析,其中主要解析方法是在next()
中实现
最后在com.sun.beans.decoder.DocumentHandler#startElement
中开始对第一个开始标签进行解析,第一个标签为<java>
,通过this.getElementHandler(var3).newInstance()
得到JavaElementHandler
的实例并赋值给this.handler
,然后对Owner
和Parent
的值进行设置,接下来在for循环中对标签内部的属性进行遍历取值,其中var4.getQName(var6)
是对标签内部的属性名称取值,var4.getValue(var6)
是对属性的值进行取值,最后调用addAttribute
设置属性
然后继续对object
标签进行遍历进行取值赋值,接下来以同样的操作对其它的开始标签进行遍历解析,直到开始解析第一个结束标签</string>
在解析第一个结束标签</string>
时,调用this.handler.endElement()
,其中StringElementHandler
中没有会根据继承关系调用父类ElementHandler
的endElement()
中
最后在StringElementHandler#getValueObject
中将值取出,然后返回一个存有该值的ValueObjectImpl
实例
然后在ElementHandler#endElement
中,将ValueObjectImpl
的值取出交给this.parent
的Handler
也就是VoidElementHandler
,这里是交给<string>
的父标签也就是<void>
标签,其中是将值存储在了数组中
|
|
接下来处理</void>
标签,最后在ObjectElementHandler#getValueObject
中,通过this.getContextBean()
得到var3的类String
,在通过var4 = var2.length == 2 ? "set" : "get"
将set
赋值给var4,最后new了一个Expression
实例,并执行var5.getValue()
将存有calc
的数组set进去
然后继续处理</array>
标签,通过this.parent.addArgument
,将存有calc
的数组添加到上一标签也就是<object>
标签的handlerObjectElementHandler
中
接下来处理<void method="start"></void>
,在处理</void>
时,在ObjectElementHandler#getValueObject
中先通过this.getContextBean()
得到了new ProcessBuilder("calc")
,然后在通过var4 = this.method != null && 0 < this.method.length() ? this.method : "new";
的判断将start
赋值给var4,然后new了一个Expression
实例,最后执行var5.getValue()
,此时拼接为new ProcessBuilder("calc").start()
,通过反射弹出计算器