介绍
漏洞介绍
国外的研究人员zero thoughts发现了一个Spring框架的反序列化远程代码执行漏洞。
spring-tx.jar包中的org.springframework.transaction.jta.JtaTransactionManager类存在JNDI反序列化问题。只要创建一个JtaTransactionManager 对象让userTransactionName指向我们注册的RMI链接(如rmi://x.x.x.x:1099/Object)然后将对象发送到有漏洞的服务器,即可执行远程命令。具体细节可以分析漏洞作者的POC代码。
漏洞影响
受影响漏洞服务器主要需要满足如下条件:
CLASSPATH 中包含了 spring-tx.jar,spring-commons.jar,javax.transaction-api.jar
存在反序列化接口如RMI, JMS, IIOP等
出现漏洞的关键jar包 spring-tx.jar,并不是spring最基本的包,默认并不使用,所以并不是所有使用了spring框架的应用都受影响,需要具体检查是否包含了spring-tx.jar包。
POC简要复现
环境描述:
Java环境——这里使用的是JDK1.7版本
Maven环境——见下面的maven包 apache-maven-3.3.9-bin.zip
Spring漏洞利用POC——spring-jndi-master.zip
环境搭建:
Java环境安装,略
Maven安装
直接解压apache-maven-3.3.9-bin.zip ,添加环境变量,然后用mvn –v测试是否安装成功
如我的maven路径:C:javaapache-maven-3.3.9
添加环境变量:
Mvn –v测试
服务器搭建:
Poc里面有两个文件夹server和client
Server用于搭建包含spring的服务器环境,client用于构建反序列化对象并发送给server
服务器搭建命令如下
首先进入server目录
然后用mvn install安装spring环境
Mvn就是开始安装的maven,server目录下有pom.xml文件。 Mvn install会根据里面的内容来下载安装spring等组件。 最后会将jar包放到target中。
java -cp “target/*” ExploitableServer 9999命令在9999端口起到有漏洞的服务器程序。
ExploitableServer.java比较简单,创建socket等待连接,读取对象。
这样便开启了一个连接
客户端攻击对象构建:
命令与上面相似:
进入目录,安装spring组件,调用代码向服务器发送指令。
Mvn install,客户端也需要安装spring的组件,来使得客户端的java代码能够构造spring对象。
java -cp “target/” ExploitClient 127.0.0.1 9999 127.0.0.1
“target/ 指定spring路径
ExploitClient 为客户端的main程序
127.0.0.1 9999 127.0.0.1三个参数分别对应为
客户端包含三个文件
ExploitClient是主程序,包含序列化的过程,一些主要出现漏洞的类,以及数据的发送。
HttpFileHandler 主要是构建字节流
ExportObject 是功能代码,构建自己的java代码,服务器有权限就能执行。
执行结果:
自检与修复:
由于主要出现漏洞的地方在于Spring-tx.jar包的JtaTransactionManager类中。 可以检测项目中是否有使用spring-tx.jar包。如果有使用可以对JtaTransactionManager类进行重写。同时有防火墙的用户可以先对数据包中的JtaTransactionManager数据进行过滤,直到源码修复。
POC网络传输数据如下,可以先对使用了JtaTransactionManager的报文进行拦截。
最近在学习反序列化之类漏洞之类的东西,之前也是看别人分析,一直没有实践,这次找到16年的一个漏洞,准备分析调试一下。漏洞环境 https://github.com/zerothoughts/spring-jndi针对上面的技术我特意整理了一下,有很多技术不是靠几句话能讲清楚,所以干脆找朋友录制了一些视频,很多问题其实答案很简单,但是背后的思考和逻辑不简单,要做到知其然还要知其所以然。如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:591240817,群里有大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家
漏洞描述
没搜到编号,反正重点是在于学习这个漏洞。首先先把服务端跑起来,服务端代码很简单,是一个serversocket的,监听9999端口,并且抑制等待客户端socket链接过来。
客户端代码
客户端流程如下:
1.客户端代码先监听8088端口,开启一个文件下载服务。其中该地址映射的就是ExportObject.class。 2.将文件地址http://127.0.0.1:8080/ExportObject.class映射到rmi服务上面
3.将ExportObject用JtaTransactionManager封装一下
4.客户端连接服务端9999,然后发送包装后的object
5.服务端接收到object,然后调用readObject 最终会弹出计算器 在分析之前有必要介绍下RMI,Remote Method Invoke 远程方法调用,用于在远端服务器上提供远程对象给客户端?什么意思,就是服务端开启这个服务,然后客户端访问服务端着个接口就可以直接拿到服务器上的对象,它的底层应该就是socket通信,然后传递一个object的。关于它的用法,
使用起来也很简单,这个被传递的对象必须继承IService,然后服务端用rebind函数将这个对象和路径映射起来就可以了,服务端关键代码如下:
IService service02 = new ServiceImpl("service02");//初始化命名空间Context namingContext = new InitialContext();//将名称绑定到对象,即向命名空间注册已经实例化的远程服务对象namingContext.rebind("rmi://localhost/service02", service02);
客户端怎么调用,也很简单
String url = "rmi://localhost/";Context namingContext = new InitialContext(); // 检索指定的对象。 即找到服务器端相对应的服务对象存根 IService service02 = (IService) namingContext.lookup(url + "service02");
只需要调用lookup将该路径传递进去就可以获得IService类的对象了,又做了这个基础后面理解起来就很简单了。
Spring框架中的JtaTransactionManager出了问题,JtaTransactionManager实现了Serializable,并重写了readObject函数,所以当服务端运行到readObject的时候
,就会调用JtaTransactionManager类的readObject函数,跟进这个函数
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {ois.defaultReadObject(); this.jndiTemplate = new JndiTemplate(); this.initUserTransactionAndTransactionManager(); this.initTransactionSynchronizationRegistry();}
其中initUserTransactionAndTransactionManager()函数的作用看意思应该是初始化一些参数,进入这个函数
继续跟进lookupUserTransaction这个函数
最终执行了lookup,如果看了前面的话应该还记得需要跟一个rmi路径,这个路径其实在ExploitClient中就设置了
所以最终被带入到lookup里面了,那么会怎样?由于路径是rmi://127.0.0.1:1099/Object ,那怎么样?new 一个Object呗,由于前面用bind将ExportObject和Object绑定了,那么就是执行ExportObject的构造函数咯,构造函数里面就是弹出一个计算器,至此,整个逻辑调通。
流程总结
用一张图来总结整个流程比较好,这里借鉴了Seebug上某片文章的图