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 ý.
Đầu tiên đọc .class đã được compile của class EvilTemplatesImpl thành mảng byte.
Sau đó tạo object TemplatesImpl với các field quan trọng được set bằng cách dùng reflection.
Sau khi hàm newTransformer() được gọi, payload ở TemplatesImpl.class được trigger.
Debug
Gadget này được trigger khi ta gọi newTransformer() của class TemplatesImpl nên ta bắt đầu đặt breakpoint từ hàm newTransformer() của class TemplatesImpl.
Sau khi hàm newTransformer() được gọi, đầu tiên nó sẽ khởi tạo Object TransformerImpl mới với tham số translet là getTransletInstance() , outputProperties là _outputProperties, identNumber là _indentNumber và tfactory là _tfactory .
Tiếp tục đặt break point tại hàm getTransletInstance() :
Để hàm defineTransletClasses() được gọi,thì _name phải khác null nên cần phải set giá trị cho _name bằng reflection.
TemplatesImpl#defineTransletClasses() là method gọi TransletClassLoader#defineClass().
publicstaticvoidmain(String[] args)throws Exception { // Đường dẫn đến file .class đã biên dịch Pathpath= Paths.get("D:\\lab\\java\\CreateClass\\target\\classes\\evil\\Hello.class"); byte[] bArr = Files.readAllBytes(path);
// Sử dụng Reflection để lấy class ClassLoader Classcls= Class.forName("java.lang.ClassLoader");
// Lấy phương thức defineClass (phương thức này mặc định là protected) Methodmethod= cls.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
// Cho phép truy cập vào phương thức protected method.setAccessible(true);
// Gọi phương thức defineClass để nạp mảng byte thành một Class thực thụ Classhello= (Class) method.invoke(ClassLoader.getSystemClassLoader(), bArr, 0, bArr.length);
// Khởi tạo một đối tượng mới của class vừa nạp (để chạy constructor của class đó) hello.newInstance(); }
Tuy nhiên 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() ).
Vậy nên nếu muốn dùng defineClass để thực thi mã bất kì khi load bytecode thì bắt buộc phải tìm cách gọi được constructor.
Load bytecode using TemplatesImpl
Do class ClassLoader khai báo defineClass với thuộc tính protected, nên ta sẽ sử dụng 1 class khác đã override method defineClass là TransletClassLoader
1 2 3 4 5 6 7 8 9
staticfinalclassTransletClassLoaderextendsClassLoader { privatefinal Map<String,Class> _loadedExternalExtensionFunctions; /** * Access to final protected superclass member from outer class. */ Class defineClass(finalbyte[] b) { return defineClass(null, b, 0, b.length); } }
Do defineClass không được khai báo scope nên scope của nó là default nên TemplatesImpl có thể gọi tới các method của nó được.
Như đã nói ở trên, mặc dù đã gọi được defineClass để load byte code, nhưng để thực thi được thì hàm newInstance() phải được gọi.
Để luồng thực thi đi tới _class[_transletIndex].newInstance(), chương trình phải đi qua nhánh if (_class == null) defineTransletClasses(); . Trong defineTransletClasses(), tồn tại một số điều kiện kiểm tra có thể khiến quá trình dừng lại hoặc không tiếp tục tới bước khởi tạo instance.
Vì ta đã set giá trị _bytecodes là mảng bytecodes của EvilTemplatesImpl.class nên _bytecodes luôn khác null.
1 2 3 4
if (_bytecodes == null) { ErrorMsgerr=newErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR); thrownewTransformerConfigurationException(err.toString()); }
Sau khi defineClass, class được load phải có superclass là AbstractTranslet thì _transletIndex mới có giá trị lớn hơn 0.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Check if this is the main class if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } }
Do đó khi tạo _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.
Ngoài ra _tfactory cần là TransformerFactoryImpl object do trong TemplatesImpl#defineTransletClasses() có gọi đến method TransformerFactoryImpl#getExternalExtensionsMap()
Vì chain được trigger khi gọi method readObject() của class PriorityQueue nên ta đặt breakpoint tại hàm này:
Tiếp tục đặt breakpoint ở hàm heapify()
Đặt breakpoint tại hàm siftDown()
Nếu field comparator khác null sẽ sử dụng siftDownUsingComparator() :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
privatevoidsiftDownUsingComparator(int k, E x) { inthalf= size >>> 1; while (k < half) { intchild= (k << 1) + 1; Objectc= queue[child]; intright= 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; }
Ở đây gọi đến method compare().
BeanComparator 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 :
Thư viện Apache Commons Beanutils cung cấp nhiều methods để làm việc với các JavaBeans 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ụ:
1
PropertyUtils.getProperty(newPerson(), "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 :
1
PropertyUtils.getProperty(a, "b.c");
Nhờ vào method này gọi được getter method của JavaBeans bất kỳ, ở đây có thể dùng nó để gọi đến TemplatesImpl#getOutputProperties()
Ta có TemplatesImpl#getOutputProperties cũng có thuộc tính public và gọi đến newTransformer().
Khi khởi tạo PriorityQueue, nếu chèn trực tiếp TemplatesImpl vào internal queue ngay từ đầu thì trong quá trình heapify (cụ thể là lúc size > 1), PriorityQueue sẽ gọi compare().
Điều này dẫn tới việc chain bị kích hoạt ngay trong lúc build payload (local), gây RCE trên chính máy tạo payload.
Vì vậy cần thêm giá trị dummy trước để khởi tạo queue an toàn rồi sau đó dùng reflection thay thế mảng queue bên trong bằng TemplatesImpl.