HIGH ssrffastapiapi keys

Ssrf in Fastapi with Api Keys

Ssrf in Fastapi with Api Keys — how this specific combination creates or exposes the vulnerability

Server-Side Request Forgery (SSRF) in FastAPI becomes particularly concerning when API keys are used for authentication because the presence of a key can make an endpoint appear safe while still allowing an attacker to force arbitrary internal requests. In many FastAPI services, developers use an API key in a header (e.g., X-API-Key) to gate functionality such as fetching a remote configuration or validating a token, but then pass user-supplied URLs or hostnames into the same request flow. If the server-side code does not validate or restrict the target host before initiating an outbound call, an attacker can supply an internal address or a metadata service URL (like http://169.254.169.254/latest/meta-data/) and the API key will be forwarded, bypassing perimeter defenses.

For example, a FastAPI route that accepts a URL and an API key, then uses requests to fetch that URL without host validation, exposes SSRF despite the key being present. The API key does not mitigate SSRF; it may actually raise the severity because the call is authenticated internally, giving the attacker a trusted channel to probe internal infrastructure. This pattern commonly maps to OWASP API Top 10 A05:2023 (Broken Function Level Authorization) and A01:2023 (Broken Object Level Authorization) when the SSRF is chained with horizontal privilege escalation via BOLA/IDOR. In a black-box scan, middleBrick tests such scenarios by submitting internal endpoints and metadata targets while supplying an API key, checking whether the API key is leaked in responses, whether internal ports are reachable, and whether the service follows unsafe redirects that lead to SSRF. The presence of an API key changes the attack surface by enabling authenticated SSRF, which can lead to data exposure, SSRF-to-RCE paths when combined with other vulnerabilities, and evasion of IP-based allowlists that assume authentication is required before trust is granted.

middleBrick detects this risk under its Data Exposure and Input Validation checks during unauthenticated and authenticated-style probing, including scenarios where an API key is supplied. Because the scanner performs black-box testing, it can identify whether the endpoint reflects internal responses, follows open redirects, or allows metadata service access even when credentials are provided. Findings include evidence of the SSRF pattern, the exact request used, and guidance on how to remediate, such as strict URL allowlisting and network segregation. The scanner also checks whether the API key is inadvertently echoed or logged, which would amplify the impact by exposing credentials through the SSRF vector.

Api Keys-Specific Remediation in Fastapi — concrete code fixes

To remediate SSRF in FastAPI when API keys are used, you must validate and restrict outbound destinations before making any request, and avoid forwarding user-controlled URLs or hosts even when an API key is present. Below are concrete code examples that demonstrate secure patterns.

Example 1: Strict host allowlist with httpx

Use an allowlist of domains and validate the parsed URL before performing any outbound call. This prevents internal hosts and metadata services from being targeted even if the request includes a valid API key.

from fastapi import FastAPI, Header, HTTPException, Depends
import httpx
from urllib.parse import urlparse

app = FastAPI()

ALLOWED_HOSTS = {"api.example.com", "internal.example.com"}

def validate_url(url: str):
    parsed = urlparse(url)
    if parsed.hostname not in ALLOWED_HOSTS:
        raise HTTPException(status_code=400, detail="Destination not allowed")
    # Optionally enforce HTTPS
    if parsed.scheme != "https":
        raise HTTPException(status_code=400, detail="Only HTTPS allowed")
    return url

@app.get("/fetch")
def fetch_resource(target: str, x_api_key: str = Header(None)):
    # Validate destination before using the API key
    url = validate_url(target)
    headers = {"X-API-Key": x_api_key} if x_api_key else {}
    with httpx.Client(timeout=5.0) as client:
        resp = client.get(url, headers=headers)
    resp.raise_for_status()
    return {"status": resp.status_code, "data": resp.text[:200]}

Example 2: Outbound proxy with destination validation and no URL forwarding

Avoid forwarding user input as the full URL. Instead, use an internal lookup to determine the target and apply network-level restrictions.

from fastapi import FastAPI, Header, HTTPException
import httpx

app = FastAPI()

RESOLVED_ENDPOINTS = {
    "weather": "https://api.weather.example.com/v1/current",
    "rates": "https://finance.example.com/latest",
}

@app.get("/data/{name}")
def get_data(name: str, x_api_key: str = Header(None)):
    if name not in RESOLVED_ENDPOINTS:
        raise HTTPException(status_code=404, detail="Endpoint not found")
    url = RESOLVED_ENDPOINTS[name]
    headers = {"X-API-Key": x_api_key} if x_api_key else {}
    with httpx.Client(timeout=5.0, proxies={"http": None, "https": None}) as client:
        resp = client.get(url, headers=headers)
    resp.raise_for_status()
    return {"name": name, "status": resp.status_code}

Example 3: Reject private IPs and localhost

Implement a helper that rejects private IP ranges, localhost, and common metadata prefixes even when an API key is provided.

import ipaddress
from fastapi import HTTPException

def is_safe_host(url: str) -> bool:
    parsed = urlparse(url)
    try:
        ip = ipaddress.ip_address(parsed.hostname or "")
        if ip.is_private or ip.is_loopback or ip.is_reserved:
            return False
    except ValueError:
        # hostname may not be an IP; rely on allowlist for domains
        if parsed.hostname in {"localhost", "127.0.0.1", "[::1]"}:
            return False
        if parsed.hostname and parsed.hostname.endswith(".internal.example.com"):
            return False
    return True

@app.post("/generic")
def generic_fetch(url: str, x_api_key: str = Header(None)):
    if not is_safe_host(url):
        raise HTTPException(status_code=400, detail="Unsafe destination")
    headers = {"X-API-Key": x_api_key} if x_api_key else {}
    with httpx.Client(follow_redirects=False, timeout=5.0) as client:
        resp = client.get(url, headers=headers)
    # Do not echo API key or internal headers
    return {"status": resp.status_code}

These examples emphasize that API keys should not be used to authorize dangerous operations like arbitrary URL fetching. Instead, limit destinations, disable redirects, set timeouts, disable proxies for outbound calls, and avoid logging or echoing the key. middleBrick can validate these controls by scanning endpoints that accept user-supplied URLs and API keys, ensuring that SSRF risks are identified and that remediation guidance aligns with secure coding practices.

Related CWEs: ssrf

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

Frequently Asked Questions

Can an API key stop SSRF if the endpoint validates the URL?
No. An API key does not prevent SSRF; it only authenticates the request. SSRF is prevented by strict destination allowlisting, rejecting private IPs, and avoiding forwarding user-controlled URLs.
How does middleBrick detect SSRF in authenticated-style scans?
middleBrick submits internal and metadata targets while including the provided API key, checking whether the key is forwarded, whether internal hosts respond, and whether unsafe redirects or payloads are reflected.