前言
用一条简单反序列化链分析的例子来加深一下理解,熟练一下使用。
环境搭建
根据参考文章,反序列化链与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有更深的理解。