最近楼主也没有其他的时间来做漏洞研究了,读者们可以从本博上次更新的时间就可以看出来=_,=。
但是为了一直关注本楼主的朋友们,我决定拿出两年前的一个存货(其实是辣鸡洞)分享,诚意满满(大雾)。
以下是正文:
——————————————————————————————————————————————————————
TL;DR : 本文主要介绍Tomcat集群的实现机制以及如何发现了反序列化RCE漏洞,当内网渗透碰到tomcat集群或者节点暴露在公网时,可以使用本漏洞进行命令执行。唯一的缺点是有JDK版本限制,因为gadgets用的是原生JRE环境中的。
在研究Tomcat集群功能时,注意到了Session同步的功能,即在多个Tomcat Node进行请求处理的时候,自然会将Session数据进行复制分发。这就涉及到Java类如果通过网络传输的问题,因为集群内节点的同步本质上也是网络通信的过程,因此很自然的就想到了对象序列化然后传输的过程。
在Tomcat中,集群间相互通信是使用的Tribes组件。简约地说,Tribes是一个具备让你通过网络向其他成员发送和接收信息、动态检测发现其他节点的组通信能力的高扩展性的独立的消息框架。Tribes很好地将点对点、点对组的通信抽象得即简单又相对灵活。
配置Tomcat集群也很简单,本地修改server.xml,加入Cluster节点即可:
集群之间的同步过程如下,涉及到会话管理器Manager、集群对象Cluster、通信组件Tribes:
Manager检测到内容变化后,组建为消息,然后通知Cluster,Cluster负责把消息发送出去,这里实际上是Cluster依赖Tribes发送的。这里对象在传输过程中,会先进行对象序列化获得字节序,然后通过网络发送这些序列化数据,最后在接收端将其反序列化还原为对象。
Cluster实际上是实现了ChannelListener的一个监听器,当收到消息时,会触发messageReceived方法,这个方法实际上又去调用了Manager的messageReceived方法,即向上通知。因此我所关心的就是这个反序列化的过程是否是有问题的。
我们找到官方配置中默认的这个Cluster对象——SimpleTcpCluster,需要找到其底层的Tribes组件,这里是GroupChannel。根据上一节描述的通信原理,当某个节点收到了Channel中的数据后,首先会调用GroupChannel中的监听方法,然后进行处理之后,再调用SimpleTcpCluster对象的相关回调方法,通过监听器的注册完成向上层进行消息传递,这一点非常像Tomcat中的各个组件之间通过监听器实现生命周期控制的机制。扯远了,我们就重点看一下GroupChannel中的messageReceived方法:
public void messageReceived(ChannelMessage msg) { ……… //省略代码 // 对接收消息进行反序列化操作 fwd = XByteBuffer.deserialize(msg.getMessage().getBytesDirect(), 0, msg.getMessage().getLength()); Member source = msg.getAddress(); boolean rx = false; boolean delivered = false; for ( int i=0; i<channelListeners.size(); i++ ) { ChannelListener channelListener = channelListeners.get(i); if (channelListener != null && channelListener.accept(fwd, source)) { // 调用上层Cluster的监听方法 channelListener.messageReceived(fwd, source); } }//for ……… //省略代码 }
以上方法的部分无关代码被我删除了,这个方法实际上做了两件事:
(1)对Channel中的Message进行反序列化操作
(2)通知上层Cluster,即调用其messageRecieved方法,传递的是反序列化好的对象
因此我们只关注在Tribes组件层面的消息处理,Cluster层的就不用管了。
上面的代码中可以看到是调用了XByteBuffer的deserialize方法,方法如下:
我们写代码测试一下反序列化漏洞:
非常好用。
然而Tribes的加载是CommonClassloader完成的,也就是classpath在TOMCAT_HOME/lib目录下,所以一些在WEB-INF/lib下的jar就使用不了。
但是万幸的是, JDK本身的一些Gadget可以供我们利用,比如以下两个Gadget分别用来对付JDK7和8的一些版本:
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/Jdk7u21.java
https://github.com/pwntester/JRE8u20_RCE_Gadget
显然这样的话是受制于JRE环境的版本,在CVE-2016-0788中,也遇到了同样的问题,但是在该漏洞中,漏洞作者发现由于jenkins某个漏洞泄露了JarLoader的objID,通过RMI的方法可以指定用该loader去加载类。但是显然本文的场景没有这个条件。
不过我们还可以考虑一些适用场景,在tomcat部署中,很多项目会把一些webapp公用jar包放在某个目录下,达到统一管理、优化部署的目的,通常部署形式有两种:
如果有存在反序列化gadgets的jar位于以上两种情况的classpath下,那就很危险了。
当GroupChannel收到消息后,往上层层转发,最终来到了ClusterManager,这里tribes提供了两种Manager——DeltaMananger,BackupManager。其中,如果用户没有指定的话,默认使用的ClusterManager就是DeltaManager。
这里先看DeltaManager的messageReceived方法,这里按照SessionMessage的类型来调用不同的handler函数进行处理。
每个handler函数内部都有反序列化的过程,这里看handleGET_ALL_SESSIONS函数:
非常典型的反序列化漏洞,这里的data是我们外部可控的:
因此我们只需要构造一个触发漏洞的SessionMessage,使用channel发过去即可:
如果我们处在内网环境中,并且已知某个段有使用Tomcat Tribes部署了集群,那么利用这个漏洞,我们可以将集群中所有的节点getshell。因为Tomcat集群是peer-to-peer的模式,没有master或者注册中心,因此我们可以很轻松的冒充某个节点将恶意数据流发送给集群组里的其他节点。
实际上,如果在配置Cluster的Receiver时,如果address为0.0.0.0,即该节点能够接收来自外网的请求,那我们在Internet上通过扫描也能远程进行命令执行。
因此总结一下攻击条件:
攻击的过程如下:
本地运行exploit效果如下:
该漏洞的危害:
之所以出现这个严重漏洞,大致有两点比较关键:
所以,为了修复漏洞,必须严格禁止集群配置中的NioReceiver绑定到0.0.0.0,其次,如果有条件,集群节点上务必设置ACL或者iptables策略防范漏洞。
1. http://tomcat.apache.org/tomcat-8.0-doc/tribes/introduction.html
2. https://github.com/frohoff/ysoserial