fastjson反序列化
fastjson 是阿里巴巴开发的 java语言编写的高性能 JSON 库,用于将数据在 Json 和 Java Object之间相互转换。它没有用java的序列化机制,而是自定义了一套序列化机制。
提供两个主要接口:
JSON.toJSONString
和 JSON.parseObject/JSON.parse
分别实现序列化和反序列化。
先来讨论一下fastjson<=1.2.24
先构造一个User类:
1 |
|
我们利用JSON.toJSONString
方法将对象序列化为JSON字符串。
1 |
|
这样会得到输出:
我们再来看看反序列化,用JSON.parseObject
方法接受一个JSON字符串和目标类的类型作为参数,将JSON字符串转换为对应的Java对象。
1 |
|
这样会得到输出:
我们看到在反序列化的时候,JSON.parseObject
方法会再去调用一次原类的Setter方法。
@type
在我们上面的反序列化代码中,JSON.parseObject
方法我们给它固定了形参为User.class,那如果在实际环境里,有那么多的类,程序怎么知道要反序列化成什么类的对象呢?
这里就出现了一个@type
属性:@type是fastjson中的一个特殊注解,用于标识JSON字符串中的某个属性是哪个Java对象的类型。具体来说,当fastjson从JSON字符串反序列化为Java对象时,如果JSON字符串中包含@type属性,fastjson会根据该属性的值来确定反序列化后的Java对象的类型。
再来看看下面两段代码:
1 |
|
这段代码没有任何输出,因为没有指定字符串是哪个java对象序列化出来的。
1 |
|
现在我们用@tpye
属性,来指定这个字符串是User类的对象序列化出来的。
这样就会调用对应的setter和getter方法。
如果这里没有任何过滤,那么反序列化的时候就可以指定任意恶意类来实例化它。
比如这个payload:
1 |
|
这样就收到了dns请求。
AutoTypeSupport
AutoTypeSupport是Fastjson中的一个配置选项,用于控制自动类型转换的支持。默认情况下,Fastjson >= 1.2.25
会禁用自动类型转换功能,以防止潜在的安全风险。通过启用AutoTypeSupport,可以允许@type字段的解析和自动类型转换。
正是因为传入的@type类有恶意风险,为了减轻Fastjson反序列化漏洞的风险,可以通过将存在安全风险的Class全路径的Hash值存储在黑名单中的方式进行校验。Fastjson使用了Hash算法,将一系列已知存在安全风险的Class的全路径转换为Hash值,并将这些Hash值存储在黑名单中。在反序列化过程中,Fastjson会检查 @type 字段指定的Class的Hash值是否存在于黑名单中。如果存在于黑名单中,Fastjson将拒绝实例化该Class,并抛出异常,从而防止恶意攻击者执行未授权等高危操作。
现在我们把版本改为1.2.25,来试试之前的代码:
1 |
|
而启用AutoTypeSupport就可以了。
绕过1.2.25-1.2.47无需AutoType
首先给出payload
1 |
|
在未开启AutoTypeSupport的时候:
1 |
|
发现成功加载了。
原理:
通过java.lang.Class,将User类加载到Map中缓存,从而绕过AutoType的检测。因此将payload分两次发送,第一次加载,第二次执行。
过程:
第一次在调用checkAutoType的时候,我们直接到MiscCodec类:
调用到MiscCodec.deserialze(),判断键是否为”val”,如果不是直接抛出一个错误。
接着判断clazz是否为Class类,是的话调用TypeUtils.loadClass()加载strVal变量值指向的类
在TypeUtils.loadClass()函数中,成功加载User类后,就会将其缓存在Map中:
之后在扫描第二部分的JSON数据时,由于前面第一部分JSON数据中的val键值User已经缓存到Map中了,所以当此时调用TypeUtils.getClassFromMapping()时能够成功从Map中获取到缓存的类,进而在下面的判断clazz是否为空的if语句中直接return返回了,从而成功绕过checkAutoType()检测
JdbcRowSetImpl利用链
在fastjson中我们使用JdbcRowSetImpl
进行反序列化的攻击,JdbcRowSetImpl
利用链的重点就在怎么调用autoCommit
的set方法,而fastjson反序列化的特点就是会自动调用到类的set方法,所以会存在这个反序列化的问题。只要制定了@type
的类型,他就会自动调用对应的类来解析。
链子寻找
看一眼setDataSourceName
主要是来到JdbcRowSetImpl类中的setAutoCommit方法:
由于this.conn
为null
,所以会调用this.connect()
函数。
connect()
会对dataSourceName
属性进行一个InitialContext.lookup
(dataSourceName)的操作。
而lookup方法是JNDI中访问远程服务器获取远程对象的方法,其参数为服务器地址。
所以我们把dataSourceName赋值为我们恶意文件的远程地址。
这样我们就可以构造我们的利用链。在@type的类型为JdbcRowSetImpl类型的时候,JdbcRowSetImpl类就会进行实例化,那么只要将dataSourceName传给lookup方法,就可以保证能够访问到远程的攻击服务器,再使用设置autoCommit属性对lookup进行触发就可以了。从而实现rce(加载了远端的恶意class字节码并执行,达到rce效果)。
payload:
1 |
|
PS:
dataSourceName
需要放在autoCommit
的前面,因为反序列化的时候是按先后顺序来set
属性的,需要先执行setDataSourceName
,然后再执行setAutoCommit
。- rmi的url后面跟上要获取的我们远程
factory
类名,因为在lookup()
里面会提取路径下的名字作为要获取的类。
不出网利用
以下两条链版本都是1.2.24以下
TemplatesImpl利用链
影响1.2.22-1.2.24
根据TemplatesImpl公式:
1 |
|
我们需要这样的json序列化后的字符串:
1 |
|
这里或许有三点疑惑:
1、_bytecodes
前面不是刚刚说了需要字节码,为什么这里使用base64编码。
在反序列化的时候,会对字符串类型进行判断,如果是base64就会被解码成byte数组
2、_tfactory
不是需要是一个 TransformerFactoryImpl 对象吗,为什么这里为空。
因为为空会新建实例进行赋值
至于_tfactory为什么会知道是TransformerFactoryImpl呢?这是在类中已经定义好了。
1 |
|
3、_outputProperties
字段是如何调用getOutputProperties
方法的。
在字段解析之前,会对于当前字段进行一次智能匹配
没调出来,我发现他早就找到了getOutputProperties
方法,一直存着,然后看到_outputProperties
字段后,去掉前面的下划线,匹配到了getOutputProperties
方法,然后调用它。
poc
1 |
|
BCEL利用链
依赖
BasicDataSource
只需要有dbcp或tomcat-dbcp的依赖即可,dbcp即数据库连接池,在java中用于管理数据库连接,还是挺常见的。
1 |
|
BCEL简单利用
BCEL这个包中有个类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。 在ClassLoader#loadClass()中,其会判断类名是否是$$BCEL$$开头,如果是的话,将会对这个字符串进行decode。可以理解为是传统字节码的HEX编码,再将反斜线替换成$。默认情况下外层还会加一层GZip压缩。会创建一个该类,并用definclass去调用
我们可以编写一个恶意的类:calc.java
1 |
|
然后使用过BCEL提供的两个类Repository
和 Utility
来利用: Repository用于将一个Java Class先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码, Utility用于将原生的字节码转换成BCEL格式的字节码:
1 |
|
链子分析
先上payload
1 |
|
至于为什么这么写payload,后面会讲到。
先看到BasicDataSource.getConnection
方法:
跟进createDataSource
方法:
继续跟进createConnectionFactory
方法:
到这里就执行了我们的恶意代码,且driverClassLoader属性和driverClassName属性都是我们可控的。
为什么会调用getConnection
方法呢?
我们回头去查看这个POC形式,首先在{“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……}
这一整段外面再套一层{}
,这样的话会把这个整体当做一个JSONObject,会把这个当做key
,值为bbb
将这个 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对 JSON Key 自动调用 toString() 方法:
而且JSONObject是Map的子类,当调用toString的时候,会依次调用该类的getter
方法获取值。然后会以字符串的形式输出出来。所以会调用到getConnection
方法。
参考文章:
https://forum.butian.net/share/2040
https://www.cnblogs.com/R0ser1/p/15915607.html