Ssrf Server Side in Flask (Python)
Ssrf Server Side in Flask with Python — how this specific combination creates or exposes the vulnerability
Server-side request forgery (SSRF) in Flask applications written in Python occurs when an attacker can coerce the server into making arbitrary HTTP requests to internal or external endpoints. Because Flask routes typically receive attacker-controlled data (e.g., URLs or host headers) and pass them to HTTP clients like requests or urllib, insecure deserialization or naive forwarding can lead to SSRF. Common patterns include webhook handlers, URL preview features, or internal status endpoints that fetch a user-supplied URL without strict validation. SSRF can expose internal services such as metadata endpoints (e.g., 169.254.169.254 on cloud providers), database ports on localhost, or internal APIs that are not exposed to the public internet. Python-specific libraries like requests and urllib3 may follow redirects by default, allowing an attacker to pivot through internal network paths. Additionally, if the Flask app runs in a container or cloud environment, SSRF can be chained with other vulnerabilities to reach the metadata service and obtain temporary credentials. SSRF is often chained with other attacks (e.g., remote code execution) to exfiltrate data or bypass egress filtering, making it critical to validate and restrict outbound destinations in Python-based Flask services.
Python-Specific Remediation in Flask — concrete code fixes
To mitigate SSRR in Flask with Python, validate and sanitize all user-supplied inputs before using them in HTTP requests. Prefer allowlisting known-safe hosts and ports, avoid forwarding requests to user-provided URLs when possible, and disable redirects for external HTTP clients. Use strict timeouts and network controls to limit the impact of a compromised endpoint.
Example: Unsafe Flask endpoint with SSRR risk
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/fetch")
def unsafe_fetch():
url = request.args.get("url")
# Risk: attacker-controlled URL passed directly to requests
resp = requests.get(url, timeout=5)
return jsonify({
"status_code": resp.status_code,
"content_length": len(resp.content)
})
Example: Safe Flask endpoint with host allowlist and input validation
import requests
from flask import Flask, request, jsonify, abort
from urllib.parse import urlparse
app = Flask(__name__)
ALLOWED_HOSTS = {"api.example.com", "data.example.com"}
ALLOWED_PORTS = {80, 443}
def is_allowed_url(url: str) -> bool:
parsed = urlparse(url)
if parsed.scheme not in {"http", "https"}:
return False
if parsed.hostname not in ALLOWED_HOSTS:
return False
if parsed.port not in ALLOWED_PORTS:
return False
return True
@app.route("/fetch-safe")
def safe_fetch():
url = request.args.get("url")
if not url or not is_allowed_url(url):
abort(400, description="Invalid or disallowed URL")
resp = requests.get(url, timeout=5, allow_redirects=False)
return jsonify({
"status_code": resp.status_code,
"content_length": len(resp.content)
})
Additional Python-specific measures
- Disable automatic redirects: use
allow_redirects=Falseinrequeststo prevent open redirects or SSRF pivots via 3xx responses. - Restrict source IPs where possible (e.g., bind outbound connections to a specific interface) and enforce timeouts to avoid resource exhaustion.
- If you must proxy arbitrary URLs, use a dedicated proxy service with strict egress controls rather than forwarding directly from the Flask process.
- Validate Host and X-Forwarded-Host headers to prevent host header poisoning that can be leveraged in SSRF chains.