HIGH ssrfflask

Ssrf in Flask

How SSRF Manifests in Flask

Server-Side Request Forgery (SSRF) in Flask applications often emerges from endpoints that accept URLs as parameters and make HTTP requests based on user input. Flask's simplicity and flexibility can inadvertently create SSRF vulnerabilities when developers build features like webhook processing, external API integrations, or proxy services.

Consider a Flask endpoint that processes external URLs:

from flask import Flask, request
import requests

app = Flask(__name__)

@app.route('/fetch', methods=['POST'])
def fetch_url():
url = request.json.get('url')
response = requests.get(url) # SSRF vulnerability
return {'content': response.text}

This pattern is dangerous because it allows attackers to control the target URL. They can exploit this to:

  • Access internal services on localhost or private networks (192.168.x.x, 10.x.x.x, etc.)
  • Reach cloud metadata services (169.254.169.254 on AWS, GCP, Azure)
  • Trigger port scanning of internal networks
  • Access cloud instance metadata containing credentials and configuration
  • Exploit cloud-specific endpoints like /latest/meta-data/iam/security-credentials/

Flask-specific SSRF patterns include:

# Webhook processing endpoint
@app.route('/webhook', methods=['POST'])
def process_webhook():
webhook_url = request.json.get('webhook_url')
payload = request.json.get('payload')
requests.post(webhook_url, json=payload) # SSRF here

Another common Flask SSRF scenario involves proxy functionality:

@app.route('/proxy', methods=['GET'])
def proxy():
target_url = request.args.get('url')

Flask's dynamic routing can also introduce SSRF when combined with URL construction:

@app.route('/api/v1/<path:resource>')
def api_proxy(resource):
base_url = 'http://internal-service/'
full_url = base_url + resource # SSRF if resource is attacker-controlled

Flask-Specific Detection

Detecting SSRF in Flask applications requires both manual code review and automated scanning. middleBrick's black-box scanning approach is particularly effective for Flask applications because it tests the actual runtime behavior without requiring source code access.

middleBrick scans Flask endpoints by submitting crafted payloads to identify SSRF vulnerabilities. The scanner tests for:

  • Localhost and loopback access (127.0.0.1, localhost, ::1)
  • Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
  • Cloud metadata services (169.254.169.254, 169.254.170.2)
  • Common internal ports (22, 80, 443, 3306, 5432, 6379)
  • Protocol smuggling attempts (file://, gopher://, dict://)

For Flask applications, middleBrick's detection includes:

$ middlebrick scan https://yourapp.com/api/fetch
SSRF Detection Results:
✓ Localhost access blocked (127.0.0.1:80)
✓ Private network blocked (10.0.0.1:22)
⚠️ Cloud metadata service detected (169.254.169.254)
Severity: High
Path: /api/fetch
Recommendation: Implement URL allowlisting or network-level blocking

Manual detection in Flask code involves searching for patterns where user input flows to external requests:

# Search for these patterns in your Flask codebase
import requests
from urllib.request import urlopen
from http.client import HTTPConnection

Flask's request context makes SSRF detection straightforward:

# Check where request data is used in URL construction
@app.before_request
def log_request_data():
if 'url' in request.args or 'url' in request.form or 'url' in request.json:
print(f"Potential SSRF endpoint: {request.path}")

middleBrick's OpenAPI analysis can also detect SSRF risks by examining API specifications for parameters that accept URLs, then validating those findings against actual runtime behavior.

Flask-Specific Remediation

Remediating SSRF in Flask requires a defense-in-depth approach. The most effective strategy combines input validation, network controls, and safe request handling.

URL validation and allowlisting is the first line of defense:

from flask import Flask, request, jsonify
from urllib.parse import urlparse
import re

app = Flask(__name__)

# Allowlist of permitted domains
ALLOWED_DOMAINS = {'api.example.com', 'webhook.partner.com'}

def is_allowed_url(url):
try:
parsed = urlparse(url)
if not parsed.scheme or parsed.scheme not in ['http', 'https']:
return False
if parsed.netloc in ALLOWED_DOMAINS:
return True
# Block private networks
if re.match(r'^(10|172/(1[6-9]|2[0-9]|3[0-1])|192\.168)\.', parsed.netloc):
return False
return False # Default deny
except Exception:
return False

@app.route('/safe-fetch', methods=['POST'])
def safe_fetch():
url = request.json.get('url')
if not is_allowed_url(url):
return jsonify({'error': 'URL not allowed'}), 400
response = requests.get(url, timeout=10)
return jsonify({'content': response.text})

For Flask applications that must access external services, use network-level controls:

# Configure requests to use a proxy that blocks internal networks
import os
os.environ['HTTP_PROXY'] = 'http://proxy.example.com:8080'
os.environ['HTTPS_PROXY'] = 'http://proxy.example.com:8080'

Flask-specific SSRF mitigation with timeout and size limits:

from flask import Flask, request, jsonify
import requests

app = Flask(__name__)

@app.route('/fetch-with-limits', methods=['POST'])
def fetch_with_limits():
url = request.json.get('url')

# Basic validation
if not url or not url.startswith(('http://', 'https://')):
return jsonify({'error': 'Invalid URL'}), 400

try:
# Set strict timeout and size limits
response = requests.get(url, timeout=(3.0, 10.0), stream=True) # 3s connect, 10s read

# Limit response size to prevent SSRF via large responses
content = b''
for chunk in response.iter_content(1024):
content += chunk
if len(content) > 1024 * 1024: # 1MB limit
return jsonify({'error': 'Response too large'}), 413

return jsonify({'content': content.decode('utf-8', errors='ignore')})

except requests.exceptions.Timeout:
return jsonify({'error': 'Request timeout'}), 504
except requests.exceptions.RequestException:
return jsonify({'error': 'Request failed'}), 502

Integrate middleBrick into your Flask development workflow to catch SSRF issues early:

# In your Flask application's test suite
def test_ssrf_protection():
# Test with malicious URLs
test_urls = [
'http://localhost:8080',
'http://169.254.169.254/latest/meta-data/',
'http://192.168.1.1',
'file:///etc/passwd',
'gopher://localhost:80',
]

for url in test_urls:
response = client.post('/safe-fetch', json={'url': url})
assert response.status_code == 400 # Should be blocked

For production Flask applications, combine these code-level protections with network ACLs to block outbound connections to internal networks, ensuring that even if the application logic is bypassed, the network infrastructure provides a final defense layer.

Related CWEs: ssrf

CWE IDNameSeverity
CWE-918Server-Side Request Forgery (SSRF) CRITICAL
CWE-441Unintended Proxy or Intermediary (Confused Deputy) HIGH

Frequently Asked Questions

How does SSRF differ in Flask compared to other Python frameworks?
Flask's minimalist design means SSRF vulnerabilities often arise from simple patterns like direct requests.get() calls with user input. Unlike Django with its built-in security middleware, Flask requires explicit implementation of SSRF protections. The framework's flexibility with routing and request handling can inadvertently create SSRF paths when developers use dynamic URL construction or accept URL parameters without validation.
Can middleBrick detect SSRF in Flask applications without access to the source code?
Yes, middleBrick uses black-box scanning to test Flask endpoints by submitting crafted SSRF payloads and observing the responses. The scanner tests for localhost access, private IP ranges, cloud metadata services, and protocol smuggling attempts. It analyzes the runtime behavior of your Flask application's endpoints, making it effective even when you only have the deployed application URL.