日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现

發(fā)布時(shí)間:2024/7/5 javascript 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

2009年9月Spring 3.0 RC1發(fā)布后,Spring就引入了SpEL(Spring Expression Language)。對(duì)于開發(fā)者而言,引入新的工具顯然是令人興奮的,但是對(duì)于運(yùn)維人員,也許是噩耗的開始。類比Struts 2框架,會(huì)發(fā)現(xiàn)絕大部分的安全漏洞都和ognl脫不了干系。尤其是遠(yuǎn)程命令執(zhí)行漏洞,占據(jù)了多少甲方乙方工程師的夜晚/周末,這導(dǎo)致Struts 2越來越不受待見。

因此,我們有理由相信Spring引入SpEL必然增加安全風(fēng)險(xiǎn)。事實(shí)上,過去多個(gè)Spring CVE都與其相關(guān),如CVE-2017-8039、CVE-2017-4971、CVE-2016-5007、CVE-2016-4977等。

本文分析的CVE-2017-8046同樣也與SpEL有關(guān)。如果急于查看自己的應(yīng)用是否受影響和修復(fù)建議,請(qǐng)查看官方公告,或者跳至0x07漏洞修復(fù)。

Spring Data REST簡(jiǎn)介

Spring Data REST是Spring Data的一個(gè)子項(xiàng)目。關(guān)于Spring Data,引用官方介紹如下: > Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store.

It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services. This is an umbrella project which contains many subprojects that are specific to a given database. The projects are developed by working together with many of the companies and developers that are behind these exciting technologies.

一句話概括:Spring Data是對(duì)數(shù)據(jù)訪問的更高抽象。通過它,開發(fā)者進(jìn)一步從數(shù)據(jù)層解放出來,更專注于業(yè)務(wù)邏輯。不管是關(guān)系型數(shù)據(jù)還是非關(guān)系型數(shù)據(jù),利用相應(yīng)接口,開發(fā)者可以使用非常簡(jiǎn)單的代碼構(gòu)建對(duì)數(shù)據(jù)的訪問(當(dāng)然,Spring Data還有很多特性和功能,感興趣的可參考官方文檔)。

回過頭看Spring Data REST,它是一個(gè)構(gòu)建在Spring Data之上,為了幫助開發(fā)者更加容易地開發(fā)REST風(fēng)格的Web服務(wù),官方聲稱完成demo只需15分鐘。

官方提供的Demo

參照官方文檔,筆者使用Maven構(gòu)建Spring-boot應(yīng)用,數(shù)據(jù)庫為H2 Database。

1) 添加依賴,pom.xml內(nèi)容來自官方示例文檔。 2) 編寫實(shí)體類Person。

//import 省略@Entity public class Person {@Id@GeneratedValue(strategy = GenerationType.AUTO)private long id; //自增主健private String firstName;private String lastName; //getter setter省略 }

3) 編寫接口。

//import 省略//在/people處創(chuàng)建RESTful入口點(diǎn) @RepositoryRestResource(collectionResourceRel = "people", path = "people") public interface PersonRepository extends PagingAndSortingRepository<Person, Long> { //接口繼承了PagingAndSortingRepository,此接口封裝了對(duì)Person實(shí)體類的CURD,并且具備分頁和排序 }

4) Spring Boot執(zhí)行入口。

//import 省略@SpringBootApplication public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);} }

5) 編譯運(yùn)行。

數(shù)據(jù)操作測(cè)試

1)測(cè)試是否成功

2)使用POST方法添加一個(gè)數(shù)據(jù)

3)查看新加入的數(shù)據(jù)

4)使用PATCH請(qǐng)求方法更新數(shù)據(jù)

對(duì)于JSON Patch請(qǐng)求方法IETF制定了標(biāo)準(zhǔn)RFC6902。JSON Patch方法提交的數(shù)據(jù)必須包含一個(gè)path成員,用于定位數(shù)據(jù),同時(shí)還必須包含op成員,可選值如下:

op含義
add添加數(shù)據(jù)
remove刪除數(shù)據(jù)
replace修改數(shù)據(jù)
move移動(dòng)數(shù)據(jù)
copy拷貝數(shù)據(jù)
test測(cè)試給定數(shù)據(jù)與指定位置數(shù)據(jù)是否相等

比如對(duì)于上面添加的Person數(shù)據(jù),修改其lastName屬性,請(qǐng)求數(shù)據(jù)如下: > [{ “op”: “replace”, “path”: “/lastName”, “value”: “Zhang” }]

有兩點(diǎn)需要注意:

① 必須將Content-Type指定為application/json-patch+json。 ② 請(qǐng)求數(shù)據(jù)必須是json數(shù)組。

漏洞分析

漏洞分析涉及的源碼比較多,為了減少歧義和減小篇幅,約定兩點(diǎn): ① 代碼以片段[a-z]標(biāo)識(shí); ② 提到某個(gè)方法不會(huì)包含完整的方法簽名,僅提供方法名,需聯(lián)系上下文識(shí)別。

1)根據(jù)官方公告,結(jié)合GitHub 的commit,猜測(cè)漏洞出在path參數(shù)值的處理上。嘗試提交非法的path參數(shù)值,查看異常堆棧信息:

at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.setValue(MethodReference.java:355) ~[spring-expression-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.expression.spel.ast.CompoundExpression.setValue(CompoundExpression.java:95) ~[spring-expression-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.expression.spel.standard.SpelExpression.setValue(SpelExpression.java:438) ~[spring-expression-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.data.rest.webmvc.json.patch.PatchOperation.setValueOnTarget(PatchOperation.java:167) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.json.patch.ReplaceOperation.perform(ReplaceOperation.java:41) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.json.patch.Patch.apply(Patch.java:64) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.config.JsonPatchHandler.applyPatch(JsonPatchHandler.java:91) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.config.JsonPatchHandler.apply(JsonPatchHandler.java:83) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.readPatch(PersistentEntityResourceHandlerMethodArgumentResolver.java:206) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.read(PersistentEntityResourceHandlerMethodArgumentResolver.java:184) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.data.rest.webmvc.config.PersistentEntityResourceHandlerMethodArgumentResolver.resolveArgument(PersistentEntityResourceHandlerMethodArgumentResolver.java:141) ~[spring-data-rest-webmvc-2.6.6.RELEASE.jar:na]at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:158) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:128) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) ~[spring-webmvc-4.3.10.RELEASE.jar:4.3.10.RELEASE]//省略部分堆棧信息

2)既然是Patch請(qǐng)求方法,我們從org.springframework.data.rest.webmvc.config.JsonPatchHandler.apply(JsonPatchHandler.java:83)入手分析。

//片段a:public <T> T apply(IncomingRequest request, T target) throws Exception {Assert.notNull(request, "Request must not be null!");Assert.isTrue(request.isPatchRequest(), "Cannot handle non-PATCH request!");Assert.notNull(target, "Target must not be null!");if (request.isJsonPatchRequest()) {//return applyPatch(request.getBody(), target);} else {return applyMergePatch(request.getBody(), target);}}

片段a中的if判斷決定了請(qǐng)求Content-Type須指定application/json-patch+json。

//片段b: public boolean isJsonPatchRequest() {return isPatchRequest() //是否是PATCH請(qǐng)求方法//Content-Type是否與application/json-patch+json兼容&& RestMediaTypes.JSON_PATCH_JSON.isCompatibleWith(contentType);}

片段a中的if判斷為true的話,進(jìn)入applyPatch方法:

//片段c: @SuppressWarnings("unchecked") <T> T applyPatch(InputStream source, T target) throws Exception {return getPatchOperations(source).apply(target, (Class<T>) target.getClass()); }

跟進(jìn)getPatchOperations方法:

//片段d: private Patch getPatchOperations(InputStream source) {try {return new JsonPatchPatchConverter(mapper).convert(mapper.readTree(source));//通過Jackson 生成對(duì)應(yīng)的對(duì)象實(shí)例} catch (Exception o_O) {throw new HttpMessageNotReadableException(String.format("Could not read PATCH operations! Expected %s!", RestMediaTypes.JSON_PATCH_JSON), o_O);} }

片段d通過Jackson實(shí)例化對(duì)象,我們看看相關(guān)構(gòu)造函數(shù):

//片段e: public Patch(List<PatchOperation> operations) {this.operations = operations; } //片段f: public PatchOperation(String op, String path, Object value) {this.op = op;this.path = path;this.value = value;this.spelExpression = pathToExpression(path); }

對(duì)于PatchOperation對(duì)象,成員spelExpression根據(jù)path轉(zhuǎn)化而來,這一點(diǎn)對(duì)于PoC構(gòu)造非常重要,筆者一開始就坑在這里。 pathToExpression完整的調(diào)用鏈比較長(zhǎng),影響PoC的構(gòu)造關(guān)鍵在于下面兩個(gè)方法。

//片段g: private static String pathToSpEL(String path) {return pathNodesToSpEL(path.split("\\/"));//跟據(jù)斜杠分割成字符數(shù)組 } //片段h: 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;}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) {//使用.拼接字符數(shù)組//如筆者嘗試執(zhí)行touch /tmp/file,spelBuilder.append('.'); //并未在/tmp中發(fā)現(xiàn)file文件,后來發(fā)現(xiàn)應(yīng)用目錄中多了隱藏文件,} //原因就在此處spelBuilder.append(pathNode);}}String spel = spelBuilder.toString();if (spel.length() == 0) {spel = "#this";}return spel; }

回到片段C,繼續(xù)看apply:

//片段i: public <T> T apply(T in, Class<T> type) throws PatchException {for (PatchOperation operation : operations) {operation.perform(in, type);}return in; }

在RFC6902的標(biāo)準(zhǔn)中,一次PATCH請(qǐng)求允許多個(gè)操作,比如:

[{ "op": "test", "path": "/a/b/c", "value": "foo" },{ "op": "remove", "path": "/a/b/c" },{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] } ]

對(duì)于上面的請(qǐng)求數(shù)據(jù),將會(huì)順序執(zhí)行test、remove、add操作(當(dāng)前操作的”文檔”為上一次操作更新后的”文檔”)。

因此,在代碼片段i中循環(huán)每一個(gè)”操作”。假設(shè)我們提交了一個(gè)PATCH請(qǐng)求op為replace,我們接著看PatchOperation子類ReplaceOperation的perform方法:

//片段j: <T> void perform(Object target, Class<T> type) {setValueOnTarget(target, evaluateValueFromTarget(target, type)); }

調(diào)用父類PatchOperation的evaluateValueFromTarget方法:

//片段k: protected <T> Object evaluateValueFromTarget(Object targetObject, Class<T> entityType) {return value instanceof LateObjectEvaluator? ((LateObjectEvaluator) value).evaluate(spelExpression.getValueType(targetObject)) : value; }

官方在evaluateValueFromTarget方法中打了補(bǔ)丁,補(bǔ)丁的修復(fù)邏輯是檢查路徑是否合法,如果不合法則會(huì)拋出PatchException。完整的補(bǔ)丁信息可以從GitHub看對(duì)應(yīng)commit。

//片段l:protected <T> Object evaluateValueFromTarget(Object targetObject, Class<T> entityType) { - return value instanceof LateObjectEvaluator - ? ((LateObjectEvaluator) value).evaluate(spelExpression.getValueType(targetObject)) : value; + verifyPath(entityType); + + return evaluate(spelExpression.getValueType(targetObject)); + }++ protected final <T> Object evaluate(Class<T> type) { + return value instanceof LateObjectEvaluator ? ((LateObjectEvaluator) value).evaluate(type) : value; + }++ /** + * Verifies that the current path is available on the given type. + * + * @param type must not be {@literal null}. + * @return the {@link PropertyPath} representing the path. Empty if the path only consists of index lookups or append + * characters. + */+ protected final Optional<PropertyPath> verifyPath(Class<?> type) { + + String pathSource = Arrays.stream(path.split("/"))// + .filter(it -> !it.matches("\\d")) // no digits + .filter(it -> !it.equals("-")) // no "last element"s + .filter(it -> !it.isEmpty()) // + .collect(Collectors.joining(".")); + + if (pathSource.isEmpty()) { + return Optional.empty(); + } + + try { + return Optional.of(PropertyPath.from(pathSource, type)); //根據(jù)對(duì)象和路徑獲取PropertyPath + } catch (PropertyReferenceException o_O) { + throw new PatchException(String.format(INVALID_PATH_REFERENCE, pathSource, type, path), o_O); + }}

回過頭看代碼片段j,setValueOnTarget再往后走就是SpEL解析了。由于SpEL非該漏洞核心,本文不再深入。

漏洞復(fù)現(xiàn)

明白了漏洞原理之后,復(fù)現(xiàn)就非常簡(jiǎn)單了。注入表達(dá)式?jīng)]有太多限制。

漏洞修復(fù)

漏洞在9月21日披露,雖然定位為嚴(yán)重。但是筆者持續(xù)跟蹤,并未發(fā)現(xiàn)國(guó)內(nèi)哪些站點(diǎn)在跟進(jìn),不排除攻擊者利用此漏洞攻擊未打補(bǔ)丁的受影響應(yīng)用。

漏洞信息來源于官方公告。

值得注意的是,本次漏洞問題出現(xiàn)在 spring-data-rest-webmvc中。由于Spring 提供內(nèi)建的依賴解決,因此可能并不會(huì)在依賴配置文件(如Maven的pom.xml)顯式看到 spring-data-rest-webmv的依賴配置,這就是為什么官方公告還提及Spring Boot和Spring Data的緣故。

漏洞觸發(fā)條件:網(wǎng)站使用Spring Data REST提供REST Web服務(wù),版本在受影響范圍內(nèi)。

修復(fù)建議:及時(shí)升級(jí)。

參考鏈接

  • https://pivotal.io/security/cve-2017-8046
  • https://github.com/spring-projects/spring-data-rest/commit/8f269e28fe8038a6c60f31a1c36cfda04795ab45
  • http://projects.spring.io/spring-data-rest/
  • https://tools.ietf.org/html/rfc6902
  • 總結(jié)

    以上是生活随笔為你收集整理的Spring Data REST 远程代码执行漏洞(CVE-2017-8046)分析与复现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。