How to Fix an SSRF Vulnerability in a URL Fetcher
An SSRF vulnerability in a URL fetcher means your server will fetch any URL the caller supplies - including internal services on 127.0.0.1 and the cloud metadata endpoint at 169.254.169.254. Fixing it requires validating the URL and blocking internal address ranges before making any request.
What SSRF is and why it matters
Server-Side Request Forgery (SSRF) happens when a server fetches a URL chosen by
the caller with no restrictions. A /fetch?url=... endpoint is the classic target.
An attacker points it at http://127.0.0.1:8000/health to probe internal services,
or at http://169.254.169.254/latest/meta-data/ to read cloud instance credentials -
the server fetches it on their behalf, bypassing any network firewall.
Parsing and blocking internal destinations
Python's ipaddress module classifies addresses precisely. The pattern is to parse
the URL, enforce a safe scheme, resolve the hostname, and reject the request if the
target IP is in a private, loopback, or link-local range:
import socket
import ipaddress
from urllib.parse import urlparse
def _is_blocked(url):
"""Return True if the URL targets a loopback, private, or link-local address."""
try:
p = urlparse(url)
except Exception:
return True
if p.scheme not in ("http", "https"):
return True
host = (p.hostname or "").lower()
# Block by name before resolving.
if host in ("localhost", "metadata.google.internal"):
return True
# IP literal: classify directly.
try:
ip = ipaddress.ip_address(host)
return (ip.is_private or ip.is_loopback
or ip.is_link_local or ip.is_reserved
or ip.is_multicast)
except ValueError:
pass
# Hostname: resolve and classify the resulting IP.
try:
ip = ipaddress.ip_address(socket.gethostbyname(host))
return ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved
except Exception:
return False
Then guard the fetch endpoint:
@app.get("/fetch")
def fetch():
url = request.args.get("url", "")
if _is_blocked(url):
return jsonify({"error": "blocked: refusing to fetch an internal address"}), 403
resp = requests.get(url, timeout=3, allow_redirects=False)
return jsonify({"status": resp.status_code, "body": resp.text[:2000]})
What each check covers
- Scheme check -
http/httpsonly; rejectsfile://,gopher://, etc. - Name blocklist - catches
localhostandmetadata.google.internalbefore DNS. - IP literal -
ipaddress.ip_address()succeeds when the host is already dotted-decimal, andis_loopback/is_private/is_link_localclassify it without a DNS round-trip. - Hostname resolution - for non-literal hostnames,
socket.gethostbyname()resolves to an IP and the same classification runs, blocking SSRF via DNS.
Limitations to know
allow_redirects=False matters: a redirect to http://127.0.0.1/ bypasses a
one-time check. Without it, an attacker can redirect from a public URL to an internal
one after your validation. Disable redirects entirely, or re-validate the Location
header before following.
DNS rebinding is the other edge - a hostname resolves to a public IP at validation
time, then to 127.0.0.1 by the time requests.get fires. A full defense resolves
once, pins the IP, and connects to that IP directly with the Host header preserved.
Blocking the obvious ranges is the essential first layer; egress firewall rules are
the reliable backstop.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Validating URLs with urlparse and ipaddress to block internal address ranges
- Resolving hostnames and classifying the resulting IP before making a request
- Disabling redirects to prevent post-validation redirect-to-internal attacks
FAQ
What is an SSRF vulnerability?
Server-Side Request Forgery (SSRF) lets an attacker supply a URL that your server fetches on their behalf. This reaches internal services (127.0.0.1, 10.x, 192.168.x) and the cloud metadata endpoint (169.254.169.254) that are otherwise unreachable from the internet.
How do I block SSRF in Python?
Parse the URL with urlparse, enforce http/https scheme, then use Python's ipaddress module to check is_private, is_loopback, and is_link_local on the target IP. For hostnames, resolve them first with socket.gethostbyname before classifying. Return 403 if any check fails.
Why is blocking 169.254.169.254 important?
169.254.169.254 is the link-local cloud metadata endpoint available on AWS, GCP, and Azure instances. On a misconfigured instance it returns IAM credentials or API keys without authentication - SSRF to this address has been the entry point for major cloud data breaches.
Is SSRF the same as CSRF?
No. SSRF (Server-Side Request Forgery) tricks the server into making requests to internal targets it should not reach; CSRF tricks a user's browser into making authenticated requests. SSRF abuses the server, CSRF abuses the client session.
How do you remediate SSRF?
Validate the destination before fetching: resolve the hostname and reject loopback, private and internal IP ranges, and the cloud metadata IP 169.254.169.254. Prefer an allowlist of permitted hosts over a blocklist.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →