HIGH zip slipflaskhmac signatures

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_digest to avoid timing attacks.
  • os.path.normpath normalizes paths, but is not sufficient alone; you must also check that the resolved path remains within the intended directory.
  • Iterating over infolist and extracting each member individually allows per-entry validation. Avoid extractall unless 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.

Frequently Asked Questions

Does a valid Hmac prevent Zip Slip if the zip file is from a trusted source?
No. A valid Hmac confirms the file’s origin and integrity, but it does not guarantee safe internal paths. You must still validate and sanitize each entry’s path during extraction to prevent directory traversal.
What is the minimum safe practice for extracting zip files in Flask when Hmac is used?
Verify the Hmac first, then iterate over each zip member, normalize and canonicalize paths, ensure they resolve inside the intended directory, and extract member-by-member. Do not rely on extractall, and reject any path that escapes the target directory.