Ssrf Server Side in Flask with Firestore
Ssrf Server Side in Flask with Firestore — how this specific combination creates or exposes the vulnerability
Server-side request forgery (SSRF) in a Flask application that interacts with Google Cloud Firestore can occur when the app builds Firestore client requests or HTTP-based helper calls using attacker-controlled input. A common pattern is fetching or validating external resources (e.g., loading a JSON schema, reading a remote configuration, or introspecting a URL) and inadvertently passing that input into Firestore document writes, Firestore-triggered Cloud Functions HTTP clients, or libraries that perform outbound HTTP calls. Because Firestore itself does not make external HTTP requests to user-supplied URLs, the risk typically arises from custom code in Flask routes or background tasks that combine Firestore operations with HTTP clients such as requests or urllib.
For example, consider a Flask route that accepts a remote URL to populate a Firestore document with fetched metadata. If the developer uses the provided URL directly with an HTTP client before validating or sanitizing it, an attacker can supply an internal address (e.g., http://169.254.169.254/latest/meta-data/iam/security-credentials/), leading to SSRF. Even though Firestore only stores the resulting data or metadata, the malicious request originates from the Flask host, bypassing egress filtering that might block direct calls from the attacker. In server-side deployments, SSRF can be chained to reach internal services that are not exposed publicly, such as the Firestore metadata service on 169.254.169.254, cloud instance metadata, or internal APIs that trust the Flask server’s identity.
Another scenario involves webhook or callback configurations stored in Firestore. If an admin sets a webhook URL in a Firestore document and a Flask endpoint processes that stored URL by making an HTTP request, compromised or mistakenly set entries can lead to SSRF when the Flask app follows the attacker-controlled value. Because the Firestore client is initialized with application credentials, any SSRF-triggered outbound call may inherit those permissions, increasing the potential impact. The combination of Flask’s flexibility in routing and Firestore’s integration with broader Google Cloud services means that insecure handling of URLs in request logic can expose internal infrastructure and amplify an SSRF beyond simple port scanning.
To detect such patterns, security scans like those provided by middleBrick analyze the unauthenticated attack surface of Flask endpoints, including how external input flows into HTTP calls and Firestore interactions. The scanner checks whether user-controlled data can influence network destinations, whether requests are appropriately validated and restricted, and whether sensitive metadata services are reachable from the application layer. Findings highlight the need for strict URL allowlisting, removal of unnecessary HTTP clients from Firestore-driven workflows, and separation of duties between data storage and external fetching logic.
Firestore-Specific Remediation in Flask — concrete code fixes
Remediation focuses on preventing untrusted input from reaching outbound HTTP calls and ensuring Firestore operations remain strictly data-centric. Always validate and sanitize any user-controlled data before using it in network requests, and avoid using Firestore documents or fields as a transport for arbitrary URLs. Use allowlists for protocols, hosts, and ports, and prefer server-side configuration for external endpoints instead of runtime user input.
Example: Unsafe Flask route with SSRF risk
import requests
from flask import Flask, request
import google.auth.transport.requests
from google.cloud import firestore
app = Flask(__name__)
db = firestore.Client()
@app.route('/fetch-metadata')
def fetch_metadata():
url = request.args.get('url')
# Risky: user-controlled URL used directly
resp = requests.get(url, timeout=5)
data = resp.json()
doc_ref = db.collection('metadata').document('external')
doc_ref.set({'source': url, 'data': data})
return {'status': 'ok'}
Secure alternative with allowlist and configuration-driven endpoints
import re
import requests
from flask import Flask, request, abort
from google.cloud import firestore
ALLOWED_HOSTS = {'api.example.com', 'config.example.com'}
ALLOWED_SCHEMES = {'https'}
def is_allowed_target(url: str) -> bool:
parsed = __import__('urllib.parse').urlparse(url)
return parsed.scheme in ALLOWED_SCHEMES and parsed.hostname in ALLOWED_HOSTS
app = Flask(__name__)
db = firestore.Client()
@app.route('/fetch-metadata')
def fetch_metadata():
url = request.args.get('url')
if not url or not is_allowed_target(url):
abort(400, 'Invalid or disallowed URL')
resp = requests.get(url, timeout=5)
resp.raise_for_status()
data = resp.json()
doc_ref = db.collection('metadata').document('external')
doc_ref.set({'source': url, 'data': data})
return {'status': 'ok'}
In the secure version, the function is_allowed_target enforces an allowlist on hostname and scheme, preventing requests to internal or unexpected targets. This pattern ensures that even if an attacker controls the url parameter, they cannot direct the Flask app to internal services such as the Firestore metadata endpoint or cloud instance metadata. Firestore usage remains limited to storing validated external data, avoiding the use of Firestore documents as a proxy for arbitrary URLs.
Using server-side configuration instead of user input
from flask import Flask, jsonify
from google.cloud import firestore
import requests
app = Flask(__name__)
db = firestore.Client()
# Load allowed endpoints from Firestore or environment at startup
@app.before_first_request
def load_config():
global EXTERNAL_ENDPOINT
config_doc = db.collection('config').document('external_api').get()
if config_doc.exists:
EXTERNAL_ENDPOINT = config_doc.to_dict().get('url')
else:
EXTERNAL_ENDPOINT = None
@app.route('/fetch-configured')
def fetch_configured():
if not EXTERNAL_ENDPOINT:
return {'error': 'endpoint not configured'}, 503
resp = requests.get(EXTERNAL_ENDPOINT, timeout=5)
resp.raise_for_status()
return jsonify(resp.json())
This approach removes user-controlled URLs entirely, storing only approved endpoints in Firestore and reading them at initialization. The Flask app never reflects attacker-supplied URLs in outbound requests, effectively mitigating SSRF while still leveraging Firestore for configuration management.
Additional hardening recommendations
- Use short timeouts and disable redirects for HTTP clients to limit SSRF probe impact.
- Apply network-level egress controls in your environment to restrict destinations regardless of code changes.
- Audit Firestore documents and Cloud Functions for any dynamic URL assembly that includes user input.
- Treat Firestore-triggered background tasks as part of the attack surface; validate any URLs they process.