
إثبات المفهوم لثغرة إلغاء التسلسل في Spring Kafka CVE-2023-34040
أولاً، قم بتشغيل مثيل Kafka المحمول باستخدام Docker. سيؤدي هذا إلى تشغيل Kafka وجعله متاحًا على المنفذ 29092.
docker-compose up
قم بتشغيل تطبيق المستهلك.
cd spring-kafka-consumer
mvn clean install
mvn spring-boot:run
سيؤدي هذا إلى بناء وتشغيل تطبيق المستهلك. سينتظر المستهلك لمدة أقصاها 10 دقائق لاستلام رسالة قبل إيقاف التشغيل.
المنتج
cd spring-kafka-producer
mvn clean install
mvn spring-boot:run
سيؤدي هذا إلى بناء وتشغيل تطبيق المنتج. سيرسل المنتج رسالة إلى قائمة انتظار Kafka ثم يتوقف.
لكي تنجح هذه الثغرة، يجب تفعيل أحد أو كلا العلمين التاليين على المستهلك.
CheckDeserExWhenValueNull
CheckDeserExWhenKeyNull
يتم ذلك في تطبيق المستهلك تحت KafkaConsumerConfig.greetingKafkaListenerContainerFactory()
هناك حمولتان يمكن تشغيلهما، بشكل افتراضي يتم استخدام حمولة RCE.
لتفعيل حمولة DoS، قم بتعديل الطريقة KafkaApplication.sendGreetingMessage()
قم بتغيير الحمولة المضافة كرأس إلى dosPayload وأعد بناء وتشغيل المنتج.
ملاحظة: نظرًا لأن تعطيل الخدمة يحدث عند قراءة الرسالة، ولأن القراءة لا تنتهي أبدًا، تبقى الرسالة في قائمة الانتظار حتى يتم حذفها يدويًا أو انتهاء صلاحية الاحتفاظ بالرسالة.
وهذا يزيد من فعالية هجوم DoS حيث يجعل قائمة الانتظار غير قابلة للاستخدام حتى يتم التدخل اليدوي أو انتهاء صلاحية الاحتفاظ.
مما قد يؤدي إلى فقدان البيانات للرسائل المرسلة مباشرة بعد رسالة DoS.
لتفعيل حمولة RCE، قم بتعديل الطريقة KafkaApplication.sendGreetingMessage()
قم بتغيير الحمولة المضافة كرأس إلى rcePayload وأعد بناء وتشغيل المنتج. الأمر الذي يتم تشغيله افتراضيًا هو:
touch /tmp/newfile لمعرفة ما إذا كان الهجوم ناجحًا، ابحث عن ملف يسمى newfile داخل /tmp.
إذا كنت تعمل على ويندوز، يمكنك تعديل سلسلة الأمر إلى ما يناسب.
هذه الأداة هي مجرد إثبات مفهوم. لحدوث RCE في الواقع، يجب أن تتوفر فئة أداة (gadget) في مسار فئة المستهلكين.
لا يتطلب تعطيل الخدمة (DoS) وجود أي فئة أداة محددة في مسار فئة المستهلك. يعتمد على إنشاء نسخة معدلة من الفئة
org.springframework.kafka.support.serializer.DeserializationException التي تحتوي على كائن.
وهذا يسهل إضافة أي حمولة نريدها إلى الكائن المسلسل.
هذه الفئة المعدلة تسمى xrg.springframework.kafka.support.serializer.DeserializationException (لاحظ حرف x في بداية اسم الحزمة).
بمجرد حقن الحمولة، وفي هذه الحالة هجوم من نوع مليار ضحكة (billion laughs) باستخدام java.util.Set و java.lang.Object، يتم تسلسلها.
ثم يتم تعديل البيانات الثنائية لتغيير x إلى o لتطابق ما يتوقعه المستهلك.
يتم بعد ذلك إضافة فئة الاستثناء المسلسلة كرأس رسالة في كل من الرأس springDeserializerExceptionValue والرأس springDeserializerExceptionKey.
يتم قراءتها من قبل المستهلك إذا كان المفتاح أو الرسالة فارغة. بعد ذلك فقط تأكد من أن المفتاح أو الرسالة فارغة وسيقرأها المستهلك.
توجد بعض الحماية من إلغاء التسلسل داخل Spring-Kafka. في ListenerUtils.
public static DeserializationException byteArrayToDeserializationException(LogAccessor logger, byte[] value) {
try {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(value)) {
boolean first = true;
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (this.first) {
this.first = false;
Assert.state(desc.getName().equals(DeserializationException.class.getName()),
"Header does not contain a DeserializationException");
}
return super.resolveClass(desc);
}
};
return (DeserializationException) ois.readObject();
}
catch (IOException | ClassNotFoundException | ClassCastException e) {
logger.error(e, "Failed to deserialize a deserialization exception");
return null;
}
}
يمكنك رؤية أنه يتم التحقق للتأكد من أن الفئة ذات المستوى الأعلى هي org.springframework.kafka.support.serializer.DeserializationException. لكن لاحظ أنه يتم فحص الفئة ذات المستوى الأعلى فقط، ثم فقط اسم الفئة (وهو ضمن قدرة المهاجم على التعديل). لذلك يتم إلغاء تسلسل أي حمولة تحت ذلك المستوى.