Turning your application into a proxy to attack the internal network.
Many applications need to fetch data from external URLs provided by users (e.g., fetching a profile picture, rendering a webpage preview, or downloading a webhook payload). If the application doesn't validate the URL, an attacker can provide a URL pointing to the server's own internal network (like `localhost` or `169.254.169.254`).
Because the request originates from the server itself, it easily bypasses external firewalls and can access internal databases, admin panels, or cloud metadata endpoints.
Preventing SSRF is incredibly difficult because attackers use clever DNS tricks to bypass simple string filters. The best defense is to run the fetching service in a highly restricted, isolated network (VPC/Subnet) that has absolutely no access to internal systems.
# VULNERABLE: Blindly fetching a user-supplied URL
@app.route("/fetch_preview")
def fetch_preview():
url = request.args.get('url')
# DANGER: What if url is "http://localhost:6379" (Redis)?
response = requests.get(url)
return response.content
# FLAWED DEFENSE: Simple string matching
def is_safe(url):
# Attackers bypass this using:
# 1. IP Encodings: http://2130706433 (which is 127.0.0.1)
# 2. DNS Rebinding: http://local.hacker.com (resolves to 127.0.0.1)
if "127.0.0.1" in url or "localhost" in url:
return False
return True
A PDF generation service converts HTML to PDF. It allows users to link external stylesheets (``). An attacker provides HTML with ``. The server-side headless browser fetches that URL to render the PDF. The resulting PDF, which the attacker downloads, contains the server's raw AWS access keys printed directly on the page.
Why do standard string-matching blacklists (like blocking 'localhost' or '169.254...') almost always fail to prevent SSRF?