Unsachgemäße Behandlung stark komprimierter Daten (Datenverstärkung)
Beschreibung
Unsachgemäße Behandlung stark komprimierter Daten ist eine Schwachstelle, die auftritt, wenn ein Produkt versäumt, komprimierte Eingaben mit außergewöhnlich hohen Kompressionsverhältnissen ordnungsgemäß zu handhaben, die bei der Dekompression unverhältnismäßig große Ausgaben erzeugen. Ein klassisches Beispiel ist die "Dekompressionsbombe" (oder "Zip-Bombe") - eine kompakte Datei, die beim Dekomprimieren enorm expandiert und potenziell massive Systemressourcen verbraucht. Diese Schwachstelle kann sich auch durch XML-Bomben manifestieren, die rekursive Entity-Expansion verwenden.
Risiko
Dekompressionsbomben ermöglichen Denial-of-Service-Angriffe mit minimaler Angreifer-Bandbreite. Eine 42KB-Zip-Datei kann auf 4,5 Petabyte expandieren. XML-Bomben können wenige Kilobyte XML in Gigabytes an Speicher expandieren. Diese Angriffe sind besonders effektiv gegen automatisierte Systeme, die hochgeladene Dateien, E-Mail-Anhänge oder API-Payloads verarbeiten, ohne Dekompressionsverhältnisse zu prüfen. Die Angriffe können Server abstürzen lassen, Festplatten füllen, Speicher erschöpfen und längere Dienstausfälle verursachen. Verschachtelte Archive können einfache Größenprüfungen umgehen, indem sie die Bombe innerhalb mehrerer Kompressionsebenen verstecken.
Lösung
Setzen Sie Limits für die Dekompressions-Ausgabegröße relativ zur Eingabegröße. Implementieren Sie Streaming-Dekompression, die die Ausgabegröße inkrementell verfolgt. Brechen Sie die Dekompression ab, wenn die Ausgabe den Schwellenwert überschreitet. Deaktivieren Sie Entity-Expansion in XML-Parsern oder setzen Sie strikte Limits. Scannen Sie komprimierte Dateien auf verschachtelte Archive und wenden Sie Limits auf jeder Ebene an. Verwenden Sie Bibliotheken, die Dekompressionslimits unterstützen. Überwachen Sie den Ressourcenverbrauch während Dekompressionsoperationen. Erwägen Sie die Verwendung von Sandbox-Umgebungen für die Verarbeitung nicht vertrauenswürdiger komprimierter Daten.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Verfügbarkeit | Umfang: Verfügbarkeit CPU- und Speicherressourcen erschöpfen sich schnell. Die Systemleistung verschlechtert sich erheblich. Vollständige Systemabstürze oder Dienstunterbrechungen können auftreten. Denial-of-Service durch Ressourcenerschöpfung. |
Beispielcode
Anfälliger Code
# Anfällig: Kein Dekompressionslimit
import zipfile
import io
def vulnerable_extract_zip(zip_data):
# Anfällig: Extrahiert ohne Prüfung des Dekompressionsverhältnisses
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
for member in zf.namelist():
# Anfällig: Kein Größenlimit - Zip-Bombe kann Festplatte/Speicher erschöpfen
zf.extract(member, "/tmp/extracted")
# Anfällig: Gesamten dekomprimierten Inhalt in Speicher lesen
def vulnerable_read_gzip(gzip_data):
import gzip
# Anfällig: Dekomprimiert gesamte Datei in Speicher
return gzip.decompress(gzip_data)
// Anfällig: Keine Dekompressionsverhältnisprüfung
import java.util.zip.*;
public class VulnerableDecompressor {
public void extractZip(InputStream zipStream, String destDir)
throws IOException {
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// Anfällig: Keine Größenprüfung vor Extraktion
File file = new File(destDir, entry.getName());
try (FileOutputStream fos = new FileOutputStream(file)) {
byte[] buffer = new byte[8192];
int len;
// Anfällig: Extrahiert unbegrenzte Daten
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
}
}
}
<!-- Anfällig: XML-Bombe mit Entity-Expansion -->
<?xml version="1.0"?>
<!DOCTYPE bomb [
<!ENTITY a "AAAAAAAAAA">
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;">
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
<!ENTITY e "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
<!ENTITY f "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
<!-- Jede Ebene multipliziert mit 10: 10^6 = 1.000.000 'A' Zeichen -->
]>
<bomb>&f;</bomb>
Korrigierter Code
# Korrigiert: Dekompressionslimits implementieren
import zipfile
import io
MAX_DECOMPRESSION_RATIO = 100 # Max 100x Expansion
MAX_OUTPUT_SIZE = 100 * 1024 * 1024 # 100MB max gesamt
def secure_extract_zip(zip_data, dest_dir):
input_size = len(zip_data)
total_extracted = 0
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
for member in zf.infolist():
# Korrigiert: Unkomprimierte Größe vor Extraktion prüfen
if member.file_size > MAX_OUTPUT_SIZE:
raise ValueError(f"Datei {member.filename} zu groß: {member.file_size}")
# Korrigiert: Dekompressionsverhältnis prüfen
if member.compress_size > 0:
ratio = member.file_size / member.compress_size
if ratio > MAX_DECOMPRESSION_RATIO:
raise ValueError(f"Verdächtiges Kompressionsverhältnis: {ratio}")
total_extracted += member.file_size
if total_extracted > MAX_OUTPUT_SIZE:
raise ValueError("Gesamte Extraktionsgröße überschritten")
# Korrigiert: Mit verifizierter Größe extrahieren
zf.extract(member, dest_dir)
# Korrigiert: Streaming-Dekompression mit Limit
def secure_read_gzip(gzip_data, max_size=10*1024*1024):
import gzip
import io
result = io.BytesIO()
bytes_read = 0
with gzip.GzipFile(fileobj=io.BytesIO(gzip_data)) as gz:
while True:
chunk = gz.read(8192)
if not chunk:
break
bytes_read += len(chunk)
# Korrigiert: Abbrechen wenn Limit überschritten
if bytes_read > max_size:
raise ValueError("Dekomprimierte Größe überschreitet Limit")
result.write(chunk)
return result.getvalue()
// Korrigiert: Dekompressionsverhältnis prüfen und Limits setzen
import java.util.zip.*;
public class SecureDecompressor {
private static final long MAX_OUTPUT_SIZE = 100 * 1024 * 1024; // 100MB
private static final double MAX_RATIO = 100.0;
public void extractZip(InputStream zipStream, String destDir)
throws IOException {
long totalExtracted = 0;
try (ZipInputStream zis = new ZipInputStream(zipStream)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// Korrigiert: Deklarierte Größe prüfen
if (entry.getSize() > MAX_OUTPUT_SIZE) {
throw new SecurityException("Eintrag zu groß: " + entry.getName());
}
// Korrigiert: Path-Traversal verhindern
File file = new File(destDir, entry.getName());
if (!file.getCanonicalPath().startsWith(
new File(destDir).getCanonicalPath())) {
throw new SecurityException("Path-Traversal-Versuch");
}
long entrySize = 0;
try (FileOutputStream fos = new FileOutputStream(file)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) > 0) {
entrySize += len;
totalExtracted += len;
// Korrigiert: Limits während Extraktion prüfen
if (entrySize > MAX_OUTPUT_SIZE ||
totalExtracted > MAX_OUTPUT_SIZE * 10) {
file.delete();
throw new SecurityException("Dekompressionslimit überschritten");
}
fos.write(buffer, 0, len);
}
}
// Korrigiert: Tatsächliches Verhältnis nach Extraktion prüfen
if (entry.getCompressedSize() > 0) {
double ratio = (double) entrySize / entry.getCompressedSize();
if (ratio > MAX_RATIO) {
file.delete();
throw new SecurityException("Verdächtiges Kompressionsverhältnis");
}
}
}
}
}
}
# Korrigiert: XML-Entity-Expansion deaktivieren
import defusedxml.ElementTree as ET
def secure_parse_xml(xml_string):
# Korrigiert: defusedxml verhindert Entity-Expansion-Angriffe
return ET.fromstring(xml_string)
# Alternative: lxml konfigurieren um Entities zu deaktivieren
from lxml import etree
def secure_parse_xml_lxml(xml_string):
parser = etree.XMLParser(
resolve_entities=False, # Entity-Auflösung deaktivieren
no_network=True, # Netzwerkzugriff deaktivieren
dtd_validation=False, # DTD deaktivieren
load_dtd=False
)
return etree.fromstring(xml_string.encode(), parser)
CVE-Beispiele
- CVE-2009-1955 — XML-Bomben-Schwachstelle in Apache APR-util-Bibliothek.
- CVE-2003-1564 — Parsing-Bibliothek anfällig für XML-Bomben-Angriffe.
- CVE-2019-9674 — Python zipfile-Modul anfällig für Zip-Bomben.
Referenzen
- MITRE Corporation. "CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)." https://cwe.mitre.org/data/definitions/409.html
- AeroSpike. "Zip Bomb" https://www.bamsoftware.com/hacks/zipbomb/