Kafka反序列化RCE漏洞(CVE-2023-34040)

漏洞描述

Spring Kafka 是 Spring Framework 生態系統中的一個模塊,用於簡化在 Spring 應用程序中集成 Apache Kafka 的過程,記錄 (record) 指 Kafka 消息中的一條記錄。

受影響版本中默認未對記錄配置 ErrorHandlingDeserializer,當用戶將容器屬性 checkDeserExWhenKeyNull 或 checkDeserExWhenValueNull 設置爲 true(默認爲 false),並且允許不受信任的源發佈到 Kafka 主題中時,攻擊者可將惡意 payload 注入到 Kafka 主題中,當反序列化記錄頭時遠程執行任意代碼。

影響版本

2.8.1 <= Spring-Kafka <= 2.9.103.0.0 <= Spring-Kafka <= 3.0.9

漏洞復現

這一個漏洞所影響的組件其實是 Spring-Kafka,嚴格意義上來說並不算是 kafka 的漏洞,應該算是 Spring 的漏洞。

漏洞前置知識

先來看一看 SpringBoot 和 Kafka 是怎麼完成通訊/消費的

SpringBootToKafka.png

工作流程如下

1、生產者將消息發送到 Kafka 集羣中的某個 Broker(也可以是多個)2、Kafka 集羣將消息存儲在一個或多個分區中,併爲每個分區維護一個偏移量3、消費者訂閱一個或多個主題,並從 Kafka 集羣中讀取消息。4、消費者按順序讀取每個分區中的消息,並跟蹤每個分區的偏移量。

  • ErrorHandlingDeserializer:是 Kafka中的一種反序列化器(Deserializer),它可以在反序列化過程中處理異常和錯誤。

  • checkDeserExWhenKeyNull && checkDeserExWhenValueNull:是 Kafka 中的一種序列化器(Serializer),它可以在序列化過程中檢查鍵(key/value)是否爲 null,並在發現值爲 null 時拋出異常。

再簡單整理一下漏洞條件

在受到影響的版本中,默認未對記錄配置 ErrorHandlingDeserializer容器屬性 checkDeserExWhenKeyNull 或 checkDeserExWhenValueNull 設置爲 true

環境搭建

其中需要我們起一個 Kafka 的服務,用來接收消息,本機上起比較麻煩,可以在 vps 上用 docker 迅速搭建,且需注意,Kafka 要能夠接受外連,docker-compose.yml 如下

version: '2'
​
services:
  zookeeper:
    image: zookeeper
    restart: always
    ports:
      - "2181:2181"
    container_name: zookeeper
​
  kafka:
    image: wurstmeister/kafka
    restart: always
    ports:
      - "9092:9092"
      - "9094:9094"
    depends_on:
      - zookeeper
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 124.222.21.138
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,SSL://0.0.0.0:9094
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://124.222.21.138:9092,SSL://124.222.21.138:9094
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,SSL:SSL
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
    container_name: kafka
​

Spring Kafka 的生產者和消費者可以通過使用 Spring Kafka 提供的 KafkaTemplate 和 `@KafkaListener 註解來編寫。

【----幫助網安學習,以下所有學習資料免費領!加vx:yj009991,備註 “博客園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

生產者可以使用 KafkaTemplate 來發送消息到 Kafka 集羣:

package com.drunkbaby.springkafkatest.controller;  
  
import com.drunkbaby.springkafkatest.common.KafkaInfo;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.kafka.core.KafkaTemplate;  
import org.springframework.kafka.support.SendResult;  
import org.springframework.util.concurrent.ListenableFuture;  
import org.springframework.web.bind.annotation.PostMapping;  
import org.springframework.web.bind.annotation.RequestMapping;  
import org.springframework.web.bind.annotation.RestController;  
  
import java.time.LocalDateTime;  
import java.util.concurrent.ExecutionException;  
  
@RestController  
@RequestMapping("/producer")  
public class ProducerController {  
    @Autowired  
    private KafkaTemplate<String,String> kafkaTemplate;  
  
    @PostMapping("/fireAndForget")  
    public String fireAndForget() {  
        kafkaTemplate.send(KafkaInfo.TOPIC_WELCOME, "fireAndForget:" + LocalDateTime.now());  
        return "success";  
    }  
}

消費者可以使用 @KafkaListener 註解來監聽 Kafka 集羣中的消息:

package com.drunkbaby.springkafkatest.consumer;  
  
import com.drunkbaby.springkafkatest.common.KafkaInfo;  
import org.springframework.kafka.annotation.KafkaListener;  
import org.springframework.messaging.MessageHeaders;  
import org.springframework.messaging.handler.annotation.Headers;  
import org.springframework.messaging.handler.annotation.Payload;  
import org.springframework.stereotype.Component;  
  
@Component  
public class Consumer {  
    @KafkaListener(topics = KafkaInfo.TOPIC_WELCOME)  
    public String consumer2(@Payload String message, @Headers MessageHeaders headers) {  
        System.out.println("消費者(註解方式):收到消息==> ");  
        System.out.println("  message:" + message);  
        System.out.println("  headers:");  
        headers.keySet().forEach(key -> System.out.println("    " + key + ":" + headers.get(key)));  
        return "success";  
    }

連接成功

SuccessConnectKafka.png

訪問 http://localhost:8083/producer/sync 發送一條記錄

post

構造 payload

實際影響到的是 Consumer,且 Consumer 要設置 checkDeserExWhenKeyNull 或 checkDeserExWhenValueNull 爲 true

ConcurrentKafkaListenerContainerFactory<String, Greeting> factory = new ConcurrentKafkaListenerContainerFactory<>();  
factory.getContainerProperties().setCheckDeserExWhenValueNull(true);  
factory.getContainerProperties().setCheckDeserExWhenKeyNull(true);

payload 參考 https://github.com/Contrast-Security-OSS/Spring-Kafka-POC-CVE-2023-34040

漏洞分析

主要是來看反序列化的部分

斷點會先走到 org.springframework.kafka.listener.ListenerUtils#getExceptionFromHeader 方法,它這裏面會獲取到 PoC 中的 KEY_DESERIALIZER_EXCEPTION_HEADER,並將其作爲 headers

key.png

往下跟進 byteArrayToDeserializationException() 方法,這裏就直接到反序列化的部分了,而在反序列化之前做了一次 resolveClass() 的校驗。

resolveClass.png

而這裏的 resolveClass() 校驗是一次性的,這就代表我們可以構造其他的 Payload,如 CC 鏈等,證實是可以打通的

readObject.png

之後便會進入到對應類的 readObject() 方法

漏洞修復

https://github.com/spring-projects/spring-kafka/commit/25ac793a78725e2ca4a3a2888a1506a4bfcf0c9d

相當於把這裏的 header 頭加黑了

blackHeader.png

更多網安技能的在線實操練習,請點擊這裏>>

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章