Improper Handling of Highly Compressed Data (Data Amplification)
Description
Improper Handling of Highly Compressed Data is a vulnerability that occurs when a product fails to properly manage compressed inputs with exceptionally high compression ratios that generate disproportionately large outputs upon decompression. A classic example is the "decompression bomb" (or "zip bomb")—a compact file that expands to enormous proportions when decompressed, potentially consuming massive system resources. This vulnerability can also manifest through XML bombs using recursive entity expansion.
Risk
Decompression bombs enable denial of service attacks with minimal attacker bandwidth. A 42KB zip file can expand to 4.5 petabytes. XML bombs can expand a few kilobytes of XML into gigabytes of memory. These attacks are particularly effective against automated systems that process uploaded files, email attachments, or API payloads without checking decompression ratios. The attacks can crash servers, fill disks, exhaust memory, and cause prolonged service outages. Nested archives can bypass simple size checks by hiding the bomb within multiple compression layers.
Solution
Set limits on decompression output size relative to input size. Implement streaming decompression that tracks output size incrementally. Abort decompression when output exceeds threshold. Disable entity expansion in XML parsers or set strict limits. Scan compressed files for nested archives and apply limits at each level. Use libraries that support decompression limits. Monitor resource consumption during decompression operations. Consider using sandboxed environments for processing untrusted compressed data.
Common Consequences
| Impact | Details |
|---|---|
| Availability | Scope: Availability CPU and memory resources deplete rapidly. System performance degrades significantly. Complete system crashes or service interruptions may occur. Denial of Service through resource exhaustion. |
Example Code
Vulnerable Code
# Vulnerable: No decompression limit
import zipfile
import io
def vulnerable_extract_zip(zip_data):
# Vulnerable: Extracts without checking decompression ratio
with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
for member in zf.namelist():
# Vulnerable: No size limit - zip bomb can exhaust disk/memory
zf.extract(member, "/tmp/extracted")
# Vulnerable: Reading entire decompressed content into memory
def vulnerable_read_gzip(gzip_data):
import gzip
# Vulnerable: Decompresses entire file into memory
return gzip.decompress(gzip_data)
// Vulnerable: No decompression ratio check
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) {
// Vulnerable: No size check before extraction
File file = new File(destDir, entry.getName());
try (FileOutputStream fos = new FileOutputStream(file)) {
byte[] buffer = new byte[8192];
int len;
// Vulnerable: Extracts unlimited data
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
}
}
}
<!-- Vulnerable: XML bomb using 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;">
<!-- Each level multiplies by 10: 10^6 = 1,000,000 'A' characters -->
]>
<bomb>&f;</bomb>
Fixed Code
# Fixed: Implement decompression limits
import zipfile
import io
MAX_DECOMPRESSION_RATIO = 100 # Max 100x expansion
MAX_OUTPUT_SIZE = 100 * 1024 * 1024 # 100MB max total
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():
# Fixed: Check uncompressed size before extraction
if member.file_size > MAX_OUTPUT_SIZE:
raise ValueError(f"File {member.filename} too large: {member.file_size}")
# Fixed: Check decompression ratio
if member.compress_size > 0:
ratio = member.file_size / member.compress_size
if ratio > MAX_DECOMPRESSION_RATIO:
raise ValueError(f"Suspicious compression ratio: {ratio}")
total_extracted += member.file_size
if total_extracted > MAX_OUTPUT_SIZE:
raise ValueError("Total extraction size exceeded")
# Fixed: Extract with size verified
zf.extract(member, dest_dir)
# Fixed: Streaming decompression with 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)
# Fixed: Abort if limit exceeded
if bytes_read > max_size:
raise ValueError("Decompressed size exceeds limit")
result.write(chunk)
return result.getvalue()
// Fixed: Check decompression ratio and set limits
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) {
// Fixed: Check declared size
if (entry.getSize() > MAX_OUTPUT_SIZE) {
throw new SecurityException("Entry too large: " + entry.getName());
}
// Fixed: Prevent path traversal
File file = new File(destDir, entry.getName());
if (!file.getCanonicalPath().startsWith(
new File(destDir).getCanonicalPath())) {
throw new SecurityException("Path traversal attempt");
}
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;
// Fixed: Check limits during extraction
if (entrySize > MAX_OUTPUT_SIZE ||
totalExtracted > MAX_OUTPUT_SIZE * 10) {
file.delete();
throw new SecurityException("Decompression limit exceeded");
}
fos.write(buffer, 0, len);
}
}
// Fixed: Check actual ratio after extraction
if (entry.getCompressedSize() > 0) {
double ratio = (double) entrySize / entry.getCompressedSize();
if (ratio > MAX_RATIO) {
file.delete();
throw new SecurityException("Suspicious compression ratio");
}
}
}
}
}
}
# Fixed: Disable XML entity expansion
import defusedxml.ElementTree as ET
def secure_parse_xml(xml_string):
# Fixed: defusedxml prevents entity expansion attacks
return ET.fromstring(xml_string)
# Alternative: Configure lxml to disable entities
from lxml import etree
def secure_parse_xml_lxml(xml_string):
parser = etree.XMLParser(
resolve_entities=False, # Disable entity resolution
no_network=True, # Disable network access
dtd_validation=False, # Disable DTD
load_dtd=False
)
return etree.fromstring(xml_string.encode(), parser)
CVE Examples
- CVE-2009-1955 — XML bomb vulnerability in Apache APR-util library.
- CVE-2003-1564 — Parsing library susceptible to XML bomb attacks.
- CVE-2019-9674 — Python zipfile module vulnerable to zip bombs.
References
- 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/