前言
又来炒冷饭啦,做项目终于遇到个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
只需要有dbcp
或tomcat-dbcp
的依赖即可,dbcp即数据库连接池,在java中用于管理数据库连接,还是挺常见的。
以下是一个加载恶意类,而恶意类静态代码中有写了Runtime
弹计算器的POC,老样子在exec
方法处下断点,发现调用栈还是比较钱的,可以从开头开始分析。
1 | package com.p1ay2win.fastjson; |
直接跟进到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#createASMSerializer
,ASMSerializerFactory
工厂类生成了BasicDataSource
的ASMSerializer
专属子类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 | public class BCELDecode { |
先来看看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