Server-side request forgery (SSRF) occurs when an attacker makes a server send requests to attacker-selected external or internal destinations. A URL-accepting feature, such as an avatar downloader or webhook, can become a path into services that users cannot reach directly. section 5.2 section 1.1
The impact extends beyond the vulnerable endpoint. An attacker can scan a local network, identify databases or CI services, read internal responses, or reach sensitive administrative endpoints. Weak input sanitization and unrestricted service-to-service access make the failure worse. section 5.2 section 1.1
Webhook or URL-fetching endpoint
Suppose a payment API lets customers configure webhook URLs. An attacker submits a URL such as http://localhost:8080/jobs or http://localhost:8000/secret. The server makes the request from its own network position and may return the response, exposing internal services or secrets. A similar vulnerability arises when an unrestricted GET /ssrf endpoint calls the submitted URL and returns its content. section 5.2 section 5.3
Reduce SSRF risk with layered controls:
- Prefer an allowlist of URLs, IPs, and domains that users are permitted to submit. section 5.9
- Add denylists for sensitive or malicious destinations, including localhost, 127.0.0.1, relevant IP ranges, and internal domains. section 5.3
- Separate public and administrative services through network segmentation and proper access controls. section 1.1
- Test URL-fetching features by attempting requests to internal endpoints and checking that forbidden destinations are rejected. section 5.3
Figure 5.4 Webhooks are among the most common vectors of SSRF attacks. In this example, a threat actor configures a webhook to scan the local network for a service running at http://localhost:8080/jobs.
Visualizes a webhook configured to probe a local service at http://localhost:8080/jobs. section 5.2
Listing 5.4 Mitigating the risk of SSRF attacks on the localhost
# file: ch05/ssrf.py
denylist = ["localhost", "127.0.0.1"] #1
@server.get("/ssrf")
def ssrf (url: str):
if any(domain in url for domain in denylist): #2
raise HTTPException(
status_code=422,
detail=f"Forbidden domain in {url}"
)
response = requests.get(url) #3
return response.content
Shows a basic denylist check that rejects URLs containing localhost or 127.0.0.1 with a 422 response. section 5.3
