Skip to main content

03 - Server-Side Request Forgery (SSRF)

1. The Concept (ELI5)

Imagine you are not allowed to enter the bank vault, but the bank manager is. If you trick the bank manager into going into the vault, taking a picture of the money, and sending it to you, you've successfully pulled off SSRF. You tricked the server (the manager) into making a request on your behalf to an internal resource (the vault) that you couldn't access directly.

2. The Visual

3. The Code

Vulnerable Code ❌

# Python
import requests
@app.route('/proxy')
def proxy():
url = request.args.get('url')
# VULNERABLE: Blindly fetching user-supplied URL
return requests.get(url).content
// Go
func proxyHandler(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
// VULNERABLE
resp, _ := http.Get(url)
body, _ := ioutil.ReadAll(resp.Body)
w.Write(body)
}
// TypeScript
import axios from 'axios';
app.get('/fetch-image', async (req, res) => {
const { url } = req.query;
// VULNERABLE
const response = await axios.get(url as string);
res.send(response.data);
});

Production-Ready Secure Code ✅

# Python
from urllib.parse import urlparse
import socket

def is_safe_url(url):
parsed = urlparse(url)
if parsed.scheme not in ['http', 'https']: return False
ip = socket.gethostbyname(parsed.hostname)
# Check if IP is public, block 10.0.0.0/8, 127.0.0.1, 169.254.169.254, etc.
return not ipaddress.ip_address(ip).is_private

@app.route('/proxy')
def proxy():
url = request.args.get('url')
if not is_safe_url(url): return "Blocked", 403
return requests.get(url, timeout=3).content
// Go
// Implement strict domain safelisting and block internal IP ranges
// TypeScript
// Use SSRF-protection libraries like 'ssrf-req-filter'

4. The Guardrail

rules:
- id: python-requests-ssrf
patterns:
- pattern: requests.get(request.args.get(...))
message: "SSRF risk: passing user input directly to requests.get()"
severity: ERROR
languages: [python]