flawopen.com/ssrf-c/Cpp
How libcurl requests allow internal network pivots in C/C++, and how to enforce IP address validation with CURLOPT_RESOLVE.
You send an assistant to buy a newspaper at a store. But the address you gave is 'the safe in the manager's office'. If the assistant doesn't verify that the address is a public shop, he walks into the office and hands you company secrets.
# VULNERABLE: Naive requests.get to user-provided URL
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/fetch-avatar')
def fetch_avatar():
url = request.args.get('url')
# Attacker passes: http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Server queries AWS metadata and returns cloud credentials!
resp = requests.get(url, timeout=5)
return resp.content, 200, {'Content-Type': resp.headers.get('Content-Type', 'image/png')}
# HARDENED: Resolve DNS, parse IP address, deny private/link-local CIDRs
import socket
import ipaddress
import urllib.parse
import requests
from flask import Flask, request, abort
app = Flask(__name__)
BLOCKED_NETWORKS = [
ipaddress.ip_network('127.0.0.0/8'), # Loopback
ipaddress.ip_network('10.0.0.0/8'), # RFC 1918 Private
ipaddress.ip_network('172.16.0.0/12'), # RFC 1918 Private
ipaddress.ip_network('192.168.0.0/16'), # RFC 1918 Private
ipaddress.ip_network('169.254.0.0/16'), # Link-local / Cloud IMDS
ipaddress.ip_network('::1/128'), # IPv6 Loopback
ipaddress.ip_network('fc00::/7'), # IPv6 Private ULA
]
def is_safe_ip(ip_str):
ip = ipaddress.ip_address(ip_str)
return not any(ip in net for net in BLOCKED_NETWORKS)
@app.route('/fetch-avatar')
def fetch_avatar():
url = request.args.get('url', '')
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ('http', 'https') or not parsed.hostname:
abort(400, "Invalid URL scheme")
# 1. Resolve all DNS IPs for hostname
try:
addr_info = socket.getaddrinfo(parsed.hostname, parsed.port or (443 if parsed.scheme == 'https' else 80))
resolved_ips = [ai[4][0] for ai in addr_info]
except socket.gaierror:
abort(400, "Unable to resolve host")
# 2. Verify all resolved IPs are public
for ip in resolved_ips:
if not is_safe_ip(ip):
abort(403, "Access to internal IP address is prohibited")
# 3. Fetch with redirects disabled to prevent DNS rebinding or redirect bypass
resp = requests.get(url, timeout=3, allow_redirects=False)
return resp.content, 200, {'Content-Type': resp.headers.get('Content-Type', 'image/png')}