CVE-2017-8046
https://github.com/vulhub/vulhub/tree/master/spring/CVE-2017-8046
https://github.com/vulhub/vulhub/tree/master/base/spring/spring-rest-data/2.6.6
https://github.com/spring-projects/spring-data-rest/tree/2.6.6.RELEASE
https://mp.weixin.qq.com/s/uTiWDsPKEjTkN6z9QNLtSA
https://github.com/spring-projects/spring-data-rest/compare/2.6.6.RELEASE...2.6.7.RELEASE
https://github.com/spring-projects/spring-data-rest/compare/2.6.6.RELEASE...2.6.9.RELEASE
概述
Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)
Spring Data REST是一个构建在Spring Data之上,为了帮助开发者更加容易地开发REST风格的Web服务。在REST API的Patch方法中(实现RFC6902),path的值被传入setValue
,导致执行了SpEL表达式,触发远程命令执行漏洞。
受影响的版本
- Spring Data REST versions < 2.5.12, 2.6.7, 3.0 RC3
- Spring Boot version < 2.0.0M4
- Spring Data release trains < Kay-RC3
不受影响的版本
- Spring Data REST 2.6.9
分析
分析基于 2.6.6 版本,配合 vulhub 示例的版本。
org/springframework/data/rest/webmvc/config/JsonPatchHandler.java:76
此处进行处理 patch 请求,可以看到先判断是不是 patch 请求,然后有一个 isJsonPatchRequest(判断请求头类型是不是application/json-patch+json),然后进入applyPatch
org/springframework/data/rest/webmvc/config/JsonPatchHandler.java:90
org/springframework/data/rest/webmvc/config/JsonPatchHandler.java:109
applyPatch 进入到 getPatchOperations,source 是上面传过来的请求体,在getPatchOperations中mapper``.readTree(source)
是调用的 Jackson 的方法,将请求体内容转换为 JsonNode,然后使用JsonPatchPatchConverter的 convert 方法去进行处理数据。
org/springframework/data/rest/webmvc/json/patch/JsonPatchPatchConverter.java:50
convert 代码如下,可以看到对 op 、 path 和 from 进行处理,根据 op 类型不同,创建不同的 Operation 对象
public Patch convert(JsonNode jsonNode) {
if (!(jsonNode instanceof ArrayNode)) {
throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");
}
ArrayNode opNodes = (ArrayNode) jsonNode;
List<PatchOperation> ops = new ArrayList<PatchOperation>(opNodes.size());
for (Iterator<JsonNode> elements = opNodes.elements(); elements.hasNext();) {
JsonNode opNode = elements.next();
String opType = opNode.get("op").textValue();
String path = opNode.get("path").textValue();
JsonNode valueNode = opNode.get("value");
Object value = valueFromJsonNode(path, valueNode);
String from = opNode.has("from") ? opNode.get("from").textValue() : null;
if (opType.equals("test")) {
ops.add(new TestOperation(path, value));
} else if (opType.equals("replace")) {
ops.add(new ReplaceOperation(path, value));
} else if (opType.equals("remove")) {
ops.add(new RemoveOperation(path));
} else if (opType.equals("add")) {
ops.add(new AddOperation(path, value));
} else if (opType.equals("copy")) {
ops.add(new CopyOperation(path, from));
} else if (opType.equals("move")) {
ops.add(new MoveOperation(path, from));
} else {
throw new PatchException("Unrecognized operation type: " + opType);
}
}
return new Patch(ops);
}
这里以RemoveOperation为例子,在创建时,传入 path 值,随即调用super``(``"remove"``,
path)``;
在创建时会初始化 spelExpression 对象
pathToExpression 就是将 path 通过 pathToSpEL 处理字符串后,交给 spel 进行解析
private static String pathToSpEL(String path) {
return pathNodesToSpEL(path.split("\\/"));
}
private static String pathNodesToSpEL(String[] pathNodes) {
StringBuilder spelBuilder = new StringBuilder();
for (int i = 0; i < pathNodes.length; i++) {
String pathNode = pathNodes[i];
if (pathNode.length() == 0) {
continue;
}
/*static final List<String> APPEND_CHARACTERS = Arrays.asList("-", "~")*/
if (APPEND_CHARACTERS.contains(pathNode)) {
if (spelBuilder.length() > 0) {
spelBuilder.append(".");
}
spelBuilder.append("$[true]");
continue;
}
try {
int index = Integer.parseInt(pathNode);
spelBuilder.append('[').append(index).append(']');
} catch (NumberFormatException e) {
if (spelBuilder.length() > 0) {
spelBuilder.append('.');
}
spelBuilder.append(pathNode);
}
}
String spel = spelBuilder.toString();
if (spel.length() == 0) {
spel = "#this";
}
return spel;
}
至此,相关的前置条件准备充分,然后就是如何执行 spel 表达式,回到applyPatch,getPatchOperations(source)之后会执行spply 方法,进去之后就是执行刚才解析的opration的 perform 方法,这里以RemoveOperation为例,就是去执行popValueAtPath,
修复分析
增加 verifyPath 方法校验路径是否合法,关键在于PropertyPath._from_(pathSource``,
type)``;
路径非法会抛出异常。
当调用
PropertyPath.from(pathSource, type)
时,Spring Data 会尝试解析pathSource
字符串,以创建一个PropertyPath
对象。在这个过程中,Spring Data 会对pathSource
进行一些验证来确保它是合法的。具体来说,Spring Data 在检测
pathSource
是否合法时,会执行以下步骤:语法分析:Spring Data 首先会对
pathSource
字符串进行语法分析,以解析其中的属性路径。类型检查:Spring Data 尝试根据提供的
type
参数,检查属性路径是否在该类型中是有效的。这是为了确保属性路径在给定的类型中是存在的。属性路径验证:Spring Data 会验证属性路径中的每一部分是否合法,包括属性名称是否存在、是否可访问等。
防止注入攻击:Spring Data 会对属性路径中的每一部分进行合理的验证,以防止可能的注入攻击。
但是2.6.7 版本中只有PatchOperation#evaluateValueFromTarget和 AddOperation#evaluateValueFromTarget调用了这个检查,实际能够修复影响的只有Add 、 Replace 和 Test,Remove 是不受影响的。
2.6.9版本引入SpelPath类对路径进行统一处理,都经过verifyPath 校验,相应的操作方法也都转移到 SpelPath 类中,至此问题全部解决。