Ssrf Server Side in Flask with Hmac Signatures
Ssrf Server Side in Flask with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Server-side request forgery (SSRF) in Flask applications that use HMAC signatures can still occur when user-controlled data influences the target of an outbound request, even if the request is cryptographically signed. HMACs protect integrity and authenticity of parameters, but they do not prevent the client from selecting a malicious or internal destination. For example, a developer may sign a URL parameter such as url or endpoint, verify the signature on the server, and then forward the request to the supplied target. Because the signature validates the parameter’s content rather than constraining its value, an attacker can supply any valid URI, including internal addresses like http://169.254.169.254/latest/meta-data/ or internal Kubernetes services such as http://kubernetes.default.svc.
In Flask, this typically manifests in an endpoint that accepts a target location and a signed token, verifies the HMAC, and then uses a library like requests to perform the call. Because the verification logic trusts the signed value, the server will reach out to the attacker-controlled host or internal network path. Common triggers include open redirects, webhook configurations, or any feature that allows external callbacks. Even when HMACs prevent tampering with the parameter’s structure, they do not limit which host or service is contacted, enabling SSRF to bypass trust boundaries that the developer assumed the signature enforced.
The risk is compounded when the application also trusts metadata or service discovery endpoints without additional network controls. For instance, if the signed parameter contains a service name that resolves internally, an attacker can chain SSRF with internal enumeration to scan AWS instance metadata or internal APIs. middleBrick flags such patterns under BOLA/IDOR and Data Exposure checks, noting that unauthenticated SSRF against internal endpoints often leads to lateral movement or credential exposure. This illustrates why cryptographic integrity must be complemented by strict allowlists, destination validation, and network segmentation rather than relying on signatures alone.
Hmac Signatures-Specific Remediation in Flask — concrete code fixes
To remediate SSRF when using HMAC signatures in Flask, you must separate integrity verification from destination validation. Do not allow the client to dictate the target host or port; instead, use the HMAC to sign a token that references a predefined, server-controlled action or resource. Below are two concrete patterns with syntactically correct code examples.
1. Signed token mapping to a server-side allowlist
Use the HMAC to authenticate an opaque token that maps to a verified destination on the server. This ensures the client cannot specify arbitrary URLs.
import hashlib
import hmac
import os
from flask import Flask, request, abort, jsonify
app = Flask(__name__)
SECRET_KEY = os.environ.get('WEBHOOK_SECRET', 'replace-with-256-bit-key')
ALLOWED_RESOURCES = {
'user_profile': 'https://api.example.com/v1/profile',
'account_summary': 'https://api.example.com/v1/summary',
}
def verify_signature(token, signature):
computed = hmac.new(SECRET_KEY.encode(), token.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, signature)
@app.route('/webhook', methods=['POST'])
def webhook():
token = request.json.get('token') if request.is_json else request.args.get('token')
sig = request.json.get('signature') if request.is_json else request.args.get('signature')
if not token or not sig or not verify_signature(token, sig):
abort(401, 'invalid signature')
target = ALLOWED_RESOURCES.get(token)
if target is None:
abort(400, 'unknown resource token')
# Safe: target is controlled server-side; no user URL used
import requests
resp = requests.get(target, headers={'Authorization': 'Bearer internal-token'})
return jsonify({'status': 'ok', 'data': resp.json()})
2. Signed URL components with strict host and path validation
If you must sign a URL, sign its components (path, selected query keys) and enforce a strict host and schema on the server before making the request.
from flask import Flask, request, abort, jsonify
import hashlib
import hmac
import urllib.parse
app = Flask(__name__)
SECRET_KEY = 'super-secret-key-change-in-production'
ALLOWED_HOSTS = {'api.example.com'}
def verify_signed_url(path, qs_filtered, signature):
message = f'{path}?{qs_filtered}'
expected = hmac.new(SECRET_KEY.encode(), message.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route('/fetch', methods=['GET'])
def fetch_signed():
path = request.args.get('path', '')
sig = request.args.get('sig', '')
# Build a filtered query string with only allowed parameters
allowed = {k: request.args[k] for k in ('id', 'type') if k in request.args}
qs_filtered = '&'.join(f'{k}={allowed[k]}' for k in sorted(allowed))
if not verify_signed_url(path, qs_filtered, sig):
abort(403, 'invalid signature')
# Reconstruct URL with controlled base
base = 'https://api.example.com'
if urllib.parse.urlparse(path).hostname not in ALLOWED_HOSTS:
abort(403, 'host not allowed')
target = urllib.parse.urljoin(base, path) + ('?' + qs_filtered if qs_filtered else '')
import requests
resp = requests.get(target, timeout=5)
return jsonify({'status': 'ok', 'data': resp.json()})
Both examples emphasize that HMACs secure the parameters but do not secure the destination. Remediation therefore centers on removing user-controlled hosts and paths from the request construction step, validating against strict allowlists, and applying network-level egress controls where possible. middleBrick’s checks for BOLA/IDOR and Unsafe Consumption complement these fixes by ensuring that signed tokens do not implicitly authorize access to unintended resources.