前言

用一条简单反序列化链分析的例子来加深一下理解,熟练一下使用。


环境搭建

根据参考文章,反序列化链与MimeType类有关,可以用Maven下载spring-core包作为待分析项目,解压后修改options.yml里面的appClassPath配置。

配置入口点

与前一篇入口点在配置了注解的控制器方法的情况类似,反序列化的入口点主要为equals、hashCode和toString等常见且易触发的函数,所以需要先在onStart里添加入口点:

private boolean isEntryPoint(JMethod jMethod) {
    return jMethod.getName().equals("equals") &&
            jMethod.getParamCount() == 1 &&
            jMethod.getParamType(0).getName().equals("java.lang.Object") &&
            !jMethod.getModifiers().contains(Modifier.ABSTRACT);
}

@Override
public void onStart() {
    List<JClass> list = solver.getHierarchy().applicationClasses().toList();
    for (JClass jClass : list) {
        jClass.getDeclaredMethods().forEach(jMethod->{
            if (isEntryPoint(jMethod)) {
                solver.addEntryPoint(new EntryPoint(jMethod, EmptyParamProvider.get()));
            }
        });
    }
}

sinks

然后需要修改sinks为Map.get方法,用来触发LazyMap.get接上反序列化,要求对象主体map为污染对象:

sinks:
  - { vuln: "Deserialization", level: 4, method: "<java.util.Map: java.lang.Object get(java.lang.Object)>", index: base }

sources

最重要的一环,在反序列化中,污染源主要为不具有transient和final static修饰符的成员。因此,要配置反序列化污点分析的污染源,不能像方法输入参数那样直接在onNewCSMethod里配置,需要抓住对象拥有的每一个成员。

但是具体实现时又遇到了问题,Tai-e通过makeTaint方法配置污染源需要一个SourcePoint对象,前一篇将方法输入参数配置为污染源所需的ParamSourcePoint对象只需要一个输入参数的下标。而要将成员配置为污染源需要的是FieldSourcePoint,它需要一个LoadField对象,而这个对象在我们的流程中是不好自己创造的。

翻了翻代码,在SourceHandler类里面可以找到能借鉴的处理方法,在该类中有一个loadedFieldSources表用于保存每个函数对应的LoadField对象,后续创造FieldSourcePoint需要时再取出来使用:

Set<LoadField> loads = loadedFieldSources.get(method);
if (!loads.isEmpty()) {
    Context context = csMethod.getContext();
    loads.forEach(load -> {
        Var lhs = load.getLValue();
        SourcePoint sourcePoint = new FieldSourcePoint(method, load);
        JField field = load.getFieldRef().resolve();
        Type type = fieldSources.get(field);
        Obj taint = manager.makeTaint(sourcePoint, type);
        solver.addVarPointsTo(context, lhs, taint);
    });
}

loadedFieldSources表通过onNewStmt函数进行填充,这里的fieldSources表是配置里的sources,与我们无关:

@Override
public void onNewStmt(Stmt stmt, JMethod container) {
    if (handleFieldSources && stmt instanceof LoadField loadField) {
        // Handle field sources.
        // If a {@link LoadField} loads any source fields,
        // then records the {@link LoadField} statements.
        JField field = loadField.getFieldRef().resolveNullable();
        if (fieldSources.containsKey(field)) {
            loadedFieldSources.put(container, loadField);
        }
    }
    ...
}

仿照SourceHandler类,我们首先同样要在onNewStmt里填充loadedFieldSources:

@Override
public void onNewStmt(Stmt stmt, JMethod container) {
    if (stmt instanceof LoadField loadField) {
        loadedFieldSources.put(container, loadField);
    }
}

然后在onNewCSMethod方法里添加污染源:

JMethod method = csMethod.getMethod();
Set<LoadField> loads = loadedFieldSources.get(method);
if (!loads.isEmpty()) {
    Context context = csMethod.getContext();
    loads.forEach(load -> {
        Var lhs = load.getLValue();
        SourcePoint sourcePoint = new FieldSourcePoint(method, load);
        JField field = load.getFieldRef().resolve();
        Set<Modifier> modifiers = field.getModifiers();
        if (!modifiers.contains(Modifier.TRANSIENT) &&
                !(modifiers.contains(Modifier.FINAL) && modifiers.contains(Modifier.STATIC))) {
            Obj taint = makeTaint(field.getType(), sourcePoint);
            solver.addVarPointsTo(context, lhs, taint);
        }
    });
}

最后检出了几十条链,简单观察一下发现是没有过滤掉没有继承Serializable接口的类的问题,所以再修改一下,加入继承判断:

private boolean isSerializable(JClass jClass) {
    Collection<JClass> superClasses = new HashSet<>();
    getAllSuperClasses(jClass, superClasses);
    return !superClasses.stream().filter(superClass -> superClass.getName().equals("java.io.Serializable")).toList().isEmpty();
}

private void getAllSuperClasses(JClass jClass, Collection<JClass> superClasses) {
    JClass superClass = jClass.getSuperClass();
    if (superClass != null) {
        superClasses.add(superClass);
        getAllSuperClasses(superClass, superClasses);
    }
    Collection<JClass> interfaces = jClass.getInterfaces();
    superClasses.addAll(interfaces);
    for (JClass i: interfaces) {
        getAllSuperClasses(i, superClasses);
    }
}

发现没有检测到MimeType的反序列化链,调试发现问题出在loadedFieldSources上面,里面没有parameters这个成员的信息。

仔细思考了一下,Tai-e里构造成员污染源用的是LoadField类,可能只在遇到a=b.c这种loadfield指令的时候才会构造污染源,而不是直接绑定到成员上。而onNewCSMethod又存在问题,无法捕捉到后续的MimeType函数调用,不能到时候再给要用到的成员添加污染。

因此,想搞定这个问题还需要对Tai-e有更深的理解。


参考

Spring的新入口类反序列化触发CC链


Web Java 代码分析

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Java Lambda