HIGH prototype pollutionflaskhmac signatures

Prototype Pollution in Flask with Hmac Signatures

Prototype Pollution in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Prototype pollution in Flask applications that use Hmac signatures can occur when user-controlled input flows into object construction or modification before signature verification, or when the signature is computed over a subset of data that an attacker can partially control. In Python, objects such as dictionaries and classes can have their attributes modified at runtime, enabling an attacker to inject properties that affect application behavior after the Hmac check is performed.

Consider a Flask route that accepts JSON, merges it with a configuration dictionary, and then verifies an Hmac signature provided in a header. If the merge happens before the signature is validated, an attacker can supply properties intended to pollute the merged dictionary. Because Python dictionaries are mutable and prototype-based, newly added keys can override default behavior, such as changing admin flags, redirect URLs, or serialization rules. Even when the final request includes a valid Hmac, the server may process a tampered object graph that was shaped by the attacker’s input.

When Hmac signatures are computed over only selected fields (for example, a subset of JSON or specific headers), an attacker can supply additional fields that are ignored during signing but later used by the application. This mismatch between what is signed and what is processed enables injection of malicious properties without invalidating the Hmac. In Flask, common patterns that increase risk include using request.get_json() to populate dictionaries, passing those dictionaries into functions that set attributes via setattr, or using frameworks and libraries that mutate shared configuration objects.

The interaction with Hmac verification can also be subtle: if the signature is computed over serialized data that does not canonicalize ordering or exclude attacker-supplied keys, two equivalent objects may produce different byte representations, leading to verification failures or bypasses. Moreover, if the application reuses objects across requests or stores them in global or session-level structures, polluted prototypes can persist beyond a single request, amplifying the impact across users or endpoints.

In the context of API security scanning, these issues are detectable as BOLA/IDOR and Input Validation findings when the scanner observes that object mutation occurs in areas reachable by unauthenticated input and that the Hmac scope does not cover all relevant data paths. Recognizing the specific flow where user data merges with application state before verification is essential to understanding how prototype pollution can coexist with Hmac-based integrity checks in Flask.

Hmac Signatures-Specific Remediation in Flask — concrete code fixes

To mitigate prototype pollution when using Hmac signatures in Flask, ensure that input validation and object construction occur before computing or verifying the signature, and avoid mutating shared or global objects based on user input. Canonicalize and scope the data covered by the Hmac so that the signed representation includes all and only the data that the application trusts.

Below are concrete, secure patterns for Flask routes that use Hmac signatures. Each example uses the standard library hmac and hashlib, avoids mutating request-derived objects before verification, and ensures that the signature covers a well-defined payload.

Example 1: Verify Hmac over a canonical JSON payload before any mutation

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET_KEY = b'super-secret-key'

def verify_hmac(data: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET_KEY, data, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_data()
    signature = request.headers.get('X-Signature')
    if signature is None or not verify_hmac(payload, signature):
        abort(401, 'Invalid signature')
    data = json.loads(payload)
    # Process data after verification; do not mutate shared prototypes
    process_order(data)
    return {'status': 'ok'}, 200

Example 2: Explicitly select and canonicalize fields for signing

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET_KEY = b'super-secret-key'

def build_signed_payload(data: dict) -> bytes:
    # Canonicalize by selecting only expected keys and sorting them
    subset = {'user_id': data['user_id'], 'action': data['action'], 'ts': data['ts']}
    canonical = json.dumps(subset, sort_keys=True, separators=(',', ':'))
    return canonical.encode('utf-8')

def verify_hmac_specific(data: dict, signature: str) -> bool:
    payload = build_signed_payload(data)
    expected = hmac.new(SECRET_KEY, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/action', methods=['POST'])
def action():
    data = request.get_json()
    signature = request.headers.get('X-Signature')
    if not data or not verify_hmac_specific(data, signature):
        abort(401, 'Invalid or missing signature')
    # Use only explicitly trusted fields; avoid setattr on user input
    user_id = data['user_id']
    action = data['action']
    execute_action(user_id, action)
    return {'status': 'ok'}, 200

Example 3: Reject unexpected keys and avoid global mutation

import hmac
import hashlib
import json
from flask import Flask, request, abort

app = Flask(__name__)
SECRET_KEY = b'super-secret-key'
ALLOWED_KEYS = {'user_id', 'action', 'ts', 'nonce'}

def verify_hmac_with_scope(data: dict, signature: str) -> bool:
    # Ensure no unexpected keys to prevent prototype pollution
    if not set(data.keys()).issubset(ALLOWED_KEYS):
        abort(400, 'Unexpected keys')
    payload = json.dumps(data, sort_keys=True, separators=(',', ':')).encode('utf-8')
    expected = hmac.new(SECRET_KEY, payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

@app.route('/transaction', methods=['POST'])
def transaction():
    data = request.get_json()
    signature = request.headers.get('X-Signature')
    if not data or not verify_hmac_with_scope(data, signature):
        abort(401 if signature is None else 400)
    # Process using local variables, not shared mutable state
    process_transaction(data['user_id'], data['action'], data['ts'])
    return {'status': 'success'}, 200

In summary, remediations focus on verifying the Hmac over a canonical, limited set of data before any object mutation, avoiding setattr or dynamic property assignment from user input, and ensuring that the scope of the signature covers all data used by the application. These practices reduce the risk of prototype pollution compromising the integrity of Hmac-based protections in Flask.

Frequently Asked Questions

How can I ensure my Hmac verification covers all relevant data to prevent prototype pollution?
Include all data that affects server-side behavior in the signed payload, canonicalize key ordering, and explicitly validate that incoming keys are expected before verification.
Is it safe to compute Hmac after merging user input into application objects in Flask?
No; computing Hmac after merging user input into shared objects can allow prototype pollution. Verify the signature over a controlled subset before any mutation or assignment based on user data.