Ssrf Server Side in Flask
How SSRF Manifests in Flask
Server-Side Request Forgery (SSRF) in Flask applications typically occurs when user input is used to construct URLs for internal requests without proper validation. Flask's simplicity and flexibility make it particularly vulnerable to SSRF when developers use requests, urllib, or similar libraries to fetch external resources based on user input.
A common Flask SSRF pattern appears in webhook handlers:
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
url = data['callback_url']
response = requests.get(url) # SSRF vulnerability
return {'status': 'success'}
This code allows an attacker to make the Flask application request any URL, including internal services. The vulnerability becomes severe when Flask apps run on cloud platforms where internal metadata services are accessible at predictable addresses like http://169.254.169.254 (AWS) or http://metadata.google.internal (GCP).
Flask-specific SSRF scenarios often involve:
- Proxy endpoints that fetch remote content for display
- API integration endpoints that call third-party services
- Health check endpoints that query internal services
- Image processing endpoints that download files from URLs
The danger escalates when Flask applications run in containerized environments where internal services communicate over private networks, making the SSRF attack surface much larger than the exposed API endpoints suggest.
Flask-Specific Detection
Detecting SSRF in Flask requires examining both the code patterns and runtime behavior. Code analysis should focus on endpoints that accept URLs and use HTTP clients. Look for patterns where request.args, request.form, or request.json data flows directly into URL parameters for requests.get(), urllib.request.urlopen(), or similar functions.
middleBrick's black-box scanning approach is particularly effective for Flask applications because it tests the actual running service without requiring source code access. The scanner attempts SSRF payloads against all endpoints, checking for:
- External URL requests that return unexpected content
- Internal metadata service access (AWS, GCP, Azure)
- Localhost and private IP address requests
- Port scanning behavior (attempting common service ports)
For Flask applications, middleBrick's Inventory Management check is crucial as it maps the API surface and identifies endpoints that accept URL parameters. The scanner tests these endpoints with SSRF payloads like:
http://169.254.169.254/latest/meta-data/
http://localhost:22
http://127.0.0.1:3306
http://192.168.1.1
The Encryption check also helps detect SSRF by verifying whether the application properly validates SSL certificates when making external requests, which is often bypassed in SSRF attacks.
Flask-Specific Remediation
Flask applications can implement several defense layers against SSRF. The most effective approach combines input validation, allowlisting, and safe HTTP client configuration.
Input validation should be the first line of defense:
from flask import Flask, request, abort
import requests
from urllib.parse import urlparse
app = Flask(__name__)
# Allowlist of permitted domains
ALLOWED_DOMAINS = {'api.example.com', 'images.example.com'}
def validate_url(url):
try:
result = urlparse(url)
if not all([result.scheme, result.netloc]):
return False
# Block private IP ranges and localhost
if result.hostname in {'localhost', '127.0.0.1'}:
return False
# Check for private IP ranges using ipaddress module
import ipaddress
try:
ip = ipaddress.ip_address(result.hostname)
if ip.is_private or ip.is_loopback:
return False
except ValueError:
pass # Not an IP address, could be a domain
return True
except Exception:
return False
@app.route('/fetch', methods=['GET'])
def fetch_content():
url = request.args.get('url', '')
if not validate_url(url):
abort(400, 'Invalid URL')
# Additional allowlist check
if not any(domain in url for domain in ALLOWED_DOMAINS):
abort(403, 'Domain not allowed')
try:
response = requests.get(url, timeout=5, allow_redirects=True)
response.raise_for_status()
return {'content': response.text}
except requests.RequestException:
abort(500, 'Failed to fetch content')
For Flask applications using Flask-RESTful or similar frameworks, create a custom URL field with validation:
from flask_restful import Resource, reqparse
parser = reqparse.RequestParser()
parser.add_argument('url', type=lambda x: x if validate_url(x) else None, required=True)
class ContentFetcher(Resource):
def get(self):
args = parser.parse_args()
url = args['url']
if url is None:
return {'message': 'Invalid URL'}, 400
response = requests.get(url, timeout=5)
return {'content': response.text}
The requests library can be configured to help prevent SSRF:
response = requests.get(url, timeout=5,
allow_redirects=False, # Prevent redirect-based SSRF
verify=True) # Ensure SSL validation
For Flask applications that must fetch remote content, consider using a dedicated microservice with network restrictions, or implement a proxy that validates and sanitizes requests before forwarding them.