Zip Slip in Flask with Hmac Signatures
Zip Slip in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability that occurs when an application constructs file paths from user-supplied input without proper validation. In Flask, this commonly arises when extracting files from a zip archive where the archive contains entries with crafted paths such as ../../../etc/passwd. If the server also uses Hmac Signatures to verify the integrity or origin of the zip file (e.g., to confirm the file was produced by a trusted source), the presence of a valid Hmac does not prevent malicious content inside the archive. An attacker who can influence what is signed (for example, by getting a trusted party to sign a malicious zip) or who can supply a pre-signed, malicious zip can exploit the extraction logic. The Hmac check may pass, giving a false sense of safety, while the extraction code fails to sanitize paths. This combination creates a scenario where authenticity guarantees do not imply safety: the signature confirms who created the payload, but not whether the payload’s internal structure is safe to unpack.
Consider a Flask endpoint that accepts a zip file and an Hmac header, verifies the Hmac using a shared secret, and then extracts the archive. If the extraction uses zipfile.extractall without path sanitization, an attacker can include absolute paths or sequences like ../../malicious to write outside the intended directory. The vulnerability is not in the Hmac verification itself, but in the assumption that a verified source implies safe content. Common insecure patterns include using the entry’s filename directly, failing to resolve symbolic links, or not using a safe extraction utility. Real-world attack patterns like CVE-2018-1000659 illustrate how path traversal in archive extraction can lead to arbitrary file write. In the context of OWASP API Top 10 and best practices for data exposure and input validation, server-side archive handling must treat any external input as untrusted, even when accompanied by cryptographic integrity checks.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
To remediate Zip Slip in Flask when Hmac Signatures are in use, you must validate and sanitize file paths during extraction regardless of signature verification. Always verify the Hmac first to ensure authenticity, then apply strict path checks before any filesystem operation. Use os.path.normpath and canonicalization to detect traversal attempts, and reject entries that escape the target directory. Below are concrete, secure code examples for Flask.
Secure Hmac verification and extraction in Flask
import hashlib
import hmac
import os
import zipfile
from flask import Flask, request, abort, current_app
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('HMAC_SECRET', 'change-this-secret')
def verify_hmac(data: bytes, received_hmac: str) -> bool:
secret = app.config['SECRET_KEY'].encode()
expected = hmac.new(secret, data, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received_hmac)
@app.route('/upload', methods=['POST'])
def upload_zip():
if 'file' not in request.files:
abort(400, 'file missing')
file = request.files['file']
received_hmac = request.headers.get('X-File-Hmac')
if not received_hmac:
abort(400, 'Hmac missing')
file_data = file.read()
if not verify_hmac(file_data, received_hmac):
abort(403, 'invalid Hmac')
# Safe extraction with path validation
extract_dir = '/safe/extraction/dir'
with zipfile.ZipFile(file_data) as zf:
for member in zf.infolist():
member_path = os.path.normpath(member.filename)
# Prevent path traversal: ensure the resolved path stays inside extract_dir
resolved = os.path.join(extract_dir, member_path)
if not resolved.startswith(os.path.abspath(extract_dir)):
abort(400, 'invalid file path in archive')
# Use extract member-by-member to enforce checks; do not use extractall
zf.extract(member, extract_dir)
return {'status': 'ok'}, 200
Key points in the example:
- Hmac verification uses
hmac.compare_digestto avoid timing attacks. os.path.normpathnormalizes paths, but is not sufficient alone; you must also check that the resolved path remains within the intended directory.- Iterating over
infolistand extracting each member individually allows per-entry validation. Avoidextractallunless you have previously validated all paths. - Reject entries that attempt directory traversal by ensuring the joined path starts with the absolute path of the extraction directory.
For production use, consider additional hardening: limit zip entry sizes to prevent denial-of-service, avoid following symbolic links inside archives, and log suspicious entries for audit. These measures complement the Hmac-based integrity checks and reduce the risk of path traversal even when signatures are present.