Une SSRF (Server-Side Request Forgery) est une vulnérabilité applicative où un attaquant contraint le serveur cible à effectuer une requête HTTP vers une URL qu'il choisit ou contrôle, typiquement sur un endpoint interne ou sensible non accessible depuis Internet. Le serveur devient ainsi un proxy involontaire — il fait la requête depuis sa position réseau privilégiée et renvoie potentiellement la réponse à l'attaquant. Classée CWE-918 (Common Weakness Enumeration, MITRE) et A10:2021 dans l'OWASP Top 10 Web, la SSRF est entrée dans le Top 10 via community survey en 2021, portée notamment par l'attaque Capital One 2019 qui a exfiltré 106 millions de dossiers clients via exploitation SSRF sur AWS IMDS (coût : 80 millions de dollars d'amende FINMA et OCC plus 250+ millions de frais de remédiation). Les cibles principales : services cloud metadata (AWS IMDS à 169.254.169.254, Azure IMDS, GCP metadata), services internes (bases de données, APIs admin, Redis, Elasticsearch), scan de ports réseau interne, lecture de fichiers locaux via scheme file://. Trois types structurent la classification : basic SSRF (in-band, réponse renvoyée à l'attaquant), blind SSRF (out-of-band, exploitation via canaux secondaires DNS ou timing), second-order SSRF (URL stockée et réutilisée asynchrone). La défense en profondeur s'articule en 5 couches : validation URL applicative, allowlist destinations, configuration cloud renforcée (IMDSv2 AWS obligatoire depuis novembre 2019), segmentation réseau, détection runtime. Cet article détaille la définition, le mécanisme technique, les scénarios d'exploitation, le cas Capital One, les trois types, le code vulnérable versus sécurisé, la stack de détection 2026 et la défense en profondeur complète.
1. Définition et mécanisme de la SSRF
Principe fondamental : un serveur web applicatif reçoit une entrée utilisateur contenant une URL (ex : paramètre url=, header custom, body JSON avec champ URL), et utilise cette URL pour effectuer une requête HTTP sortante sans validation stricte de la destination. L'attaquant substitue l'URL attendue par une URL pointant vers une cible interne ou sensible.
Scénario canonique
- L'application offre une fonctionnalité légitime : fetch d'une URL externe (preview de lien, webhook, proxy d'images, import depuis URL, validation de lien dans un profil utilisateur).
- Le développeur concatène l'URL utilisateur à un client HTTP (requests, axios, fetch, http.get).
- Aucune allowlist n'est appliquée, aucune validation de l'IP de destination.
- L'attaquant passe
http://169.254.169.254/latest/meta-data/(IMDS AWS) ouhttp://localhost:6379/(Redis interne) oufile:///etc/passwd. - Le serveur exécute la requête depuis sa position réseau privilégiée et renvoie (ou non) la réponse à l'attaquant.
Positionnement réseau exploité : le serveur a typiquement accès à :
- IMDS cloud (AWS 169.254.169.254, Azure idem, GCP metadata.google.internal).
- Réseau interne VPC ou VNet non exposé Internet.
- Filesystem local via scheme file://.
- Ports arbitraires internes (RFC 1918 : 10/8, 172.16/12, 192.168/16, link-local 169.254/16).
2. Les trois types de SSRF
| Type | Mécanisme | Détection | Exploitation |
|---|---|---|---|
| Basic SSRF (in-band) | Réponse de la requête forgée renvoyée directement à l'attaquant dans la réponse HTTP | Facile (diff content, regex IMDS) | Extraction directe de données sensibles |
| Blind SSRF (out-of-band) | Serveur fait la requête mais ne renvoie pas la réponse | Canaux secondaires : DNS (Burp Collaborator), timing, différences erreur HTTP | Plus complexe, validation via DNS callback |
| Second-order SSRF | URL stockée et réutilisée en traitement asynchrone (job queue, webhook retry) | Difficile en SAST, nécessite analyse flux données | Exploitation retardée, harder to detect in logs |
Blind SSRF — technique de détection via DNS callback
L'attaquant passe une URL pointant vers un domaine qu'il contrôle (ex : attacker.collaborator.oastify.com). Le serveur victime effectue la résolution DNS puis la requête HTTP. Même si la réponse HTTP n'est jamais renvoyée à l'attaquant, la requête DNS apparaît dans les logs du serveur DNS de l'attaquant, confirmant l'exploitation.
Outils standards pour validation blind SSRF
- Burp Collaborator (composant Burp Suite Professional).
- ProjectDiscovery Interactsh (open-source équivalent).
- XSS Hunter plus OAST services variés.
3. Scénarios d'exploitation concrets
Scénario 1 — Exfiltration credentials cloud via IMDS
Le scénario canonique, démontré en Capital One 2019. Payload attaquant :
- AWS IMDSv1 :
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role> - Azure IMDS :
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fmanagement.azure.com%2F - GCP :
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token(nécessite headerMetadata-Flavor: Google)
Si les credentials IAM ont des privilèges excessifs (anti-pattern fréquent avant Capital One), l'attaquant obtient un accès massif au compte cloud.
Scénario 2 — Scan de ports réseau interne
Payload attaquant : http://10.0.0.1:22/, http://10.0.0.1:3306/, http://internal-api.svc.cluster.local:8080/admin. L'attaquant identifie les services internes exposés via les différences de réponse (timing, erreur HTTP, Connection refused vs timeout). Base de données admin, consoles Kubernetes, outils CI/CD deviennent visibles.
Scénario 3 — Accès à Redis ou Elasticsearch non authentifiés
Fréquent en environnements internes où les services de cache ou moteurs de recherche sont déployés sans authentification sous l'hypothèse « réseau de confiance ». Payload : http://localhost:6379/ retourne les commandes Redis, ou gopher://localhost:6379/_*3%0d%0a$3%0d%0aSET%0d%0a... exécute des commandes Redis via gopher:// protocol (si non filtré).
Scénario 4 — Lecture de fichiers locaux via file://
Payload : file:///etc/passwd, file:///var/log/application.log, file:///proc/self/environ (dans un container). Extraction de configuration, secrets, tokens.
Scénario 5 — Bypass de l'authentification par IP source
Certaines API internes font confiance à l'IP source (anti-pattern mais fréquent en legacy). L'attaquant via SSRF fait la requête depuis le serveur applicatif, qui est dans le réseau de confiance — bypass d'authentification effectif.
4. Cas réels majeurs 2019-2024
| Incident | Année | Impact | Vecteur SSRF |
|---|---|---|---|
| Capital One | 2019 | 106 millions de dossiers clients, 80 M$ amende, 250+ M$ remédiation | SSRF sur ModSecurity WAF → AWS IMDS → credentials IAM excessifs → S3 |
| Microsoft Exchange ProxyLogon (CVE-2021-26855) | 2021 | Dizaines de milliers d'Exchange compromis | SSRF chaînée avec RCE pour auth bypass |
| Capital One avait déclenché IMDSv2 AWS | 2019 | Protection session-oriented obligatoire depuis novembre 2019 | Réponse plateforme AWS |
| GitHub Enterprise SSRF | 2022 | Disclosure coordonnée, CVE-2022-46164 | SSRF dans webhook handler |
| Ivanti Connect Secure CVE-2023-46805 plus CVE-2024-21887 | 2023-2024 | Exploitation en masse, alertes ANSSI et CERT-FR | SSRF chaînée avec auth bypass et command injection |
| Palo Alto GlobalProtect CVE-2024-3400 | 2024 | Exploit zero-day en production | SSRF chaînée avec command injection |
Observations clés
- Le SSRF seul est rarement fatal — il est chaîné avec d'autres vulnérabilités (IAM excessif, auth bypass, command injection) pour produire l'impact final.
- Les CVE récentes 2023-2024 sur Ivanti et Palo Alto illustrent que la SSRF reste critique en 2026 — pas une vulnérabilité « résolue ».
- AWS IMDSv2 (publié novembre 2019 post-Capital One) est une réponse plateforme majeure : protection session-oriented avec token PUT obligatoire et hop-limit 1 défaut.
5. Code vulnérable vs code sécurisé
Démonstration complète en Python (Flask avec requests) et Node.js (Express avec axios) — deux patterns parmi les plus courants 2026.
# owasp-a10-ssrf-python-example.py
# Exemple pedagogique OWASP A10:2021 SSRF, CWE-918.
# Code vulnerable vs code securise en Python Flask plus requests.
from flask import Flask, request, jsonify
from urllib.parse import urlparse
import ipaddress
import socket
import requests
app = Flask(__name__)
# ===== VERSION VULNERABLE (anti-pattern, NE PAS UTILISER) =====
@app.route("/fetch-vulnerable")
def fetch_vulnerable():
"""
Endpoint vulnerable SSRF.
L'URL est prise directement depuis query parameter sans validation.
Payload malveillant :
/fetch-vulnerable?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
/fetch-vulnerable?url=http://localhost:6379/
/fetch-vulnerable?url=file:///etc/passwd
"""
target_url = request.args.get("url")
if not target_url:
return jsonify({"error": "missing url"}), 400
# ANTI-PATTERN : fetch direct sans validation
response = requests.get(target_url, timeout=5)
return jsonify({
"status": response.status_code,
"content": response.text[:500]
})
# ===== VERSION SECURISEE (pattern recommande OWASP) =====
ALLOWED_HOSTNAMES = {
"api.trusted-partner.example",
"images.cdn.example",
"webhook.saas-partner.example"
}
FORBIDDEN_NETWORKS = [
ipaddress.ip_network("10.0.0.0/8"), # RFC 1918 privee
ipaddress.ip_network("172.16.0.0/12"), # RFC 1918 privee
ipaddress.ip_network("192.168.0.0/16"), # RFC 1918 privee
ipaddress.ip_network("127.0.0.0/8"), # loopback
ipaddress.ip_network("169.254.0.0/16"), # link-local incluant IMDS AWS
ipaddress.ip_network("::1/128"), # loopback IPv6
ipaddress.ip_network("fc00::/7"), # unique local IPv6
ipaddress.ip_network("fe80::/10") # link-local IPv6
]
ALLOWED_SCHEMES = {"https"}
def validate_target_url(target_url: str) -> tuple[bool, str]:
"""
Validation URL pour protection SSRF en 5 etapes.
Retourne (is_valid, reason).
"""
# 1. Parse URL
try:
parsed = urlparse(target_url)
except Exception as e:
return False, f"URL parsing error : {e}"
# 2. Verifier scheme (HTTPS only)
if parsed.scheme not in ALLOWED_SCHEMES:
return False, f"scheme non autorise : {parsed.scheme}"
# 3. Verifier hostname present
if not parsed.hostname:
return False, "hostname manquant"
# 4. Verifier hostname dans allowlist
if parsed.hostname not in ALLOWED_HOSTNAMES:
return False, f"hostname non dans allowlist : {parsed.hostname}"
# 5. Resolution DNS et verification IP contre blocklist reseaux
try:
resolved_ips = socket.getaddrinfo(parsed.hostname, None)
except socket.gaierror as e:
return False, f"resolution DNS echouee : {e}"
for family, _, _, _, sockaddr in resolved_ips:
ip_str = sockaddr[0]
try:
ip_obj = ipaddress.ip_address(ip_str)
except ValueError:
return False, f"IP resolue invalide : {ip_str}"
for forbidden_network in FORBIDDEN_NETWORKS:
if ip_obj in forbidden_network:
return False, f"IP resolue dans reseau interdit : {ip_str}"
return True, "OK"
@app.route("/fetch-secure")
def fetch_secure():
"""
Endpoint securise contre SSRF.
Validation stricte en 5 etapes avant fetch.
Conforme OWASP A10:2021 plus OWASP SSRF Prevention Cheat Sheet.
"""
target_url = request.args.get("url")
if not target_url:
return jsonify({"error": "missing url"}), 400
is_valid, reason = validate_target_url(target_url)
if not is_valid:
app.logger.warning(
f"SSRF attempt blocked : url={target_url} reason={reason} "
f"ip={request.remote_addr}"
)
return jsonify({"error": "URL non autorisee"}), 403
# Fetch avec timeout strict plus limite taille reponse
try:
response = requests.get(
target_url,
timeout=5,
allow_redirects=False, # pas de suivi redirect (bypass potentiel)
stream=True
)
# Limite taille reponse a 1 MB
content = response.raw.read(1024 * 1024, decode_content=True)
return jsonify({
"status": response.status_code,
"content": content.decode("utf-8", errors="replace")[:500]
})
except requests.RequestException as e:
return jsonify({"error": f"fetch error : {e}"}), 500
if __name__ == "__main__":
app.run(debug=False)Version Node.js équivalente avec axios et la librairie ssrf-req-filter :
// owasp-a10-ssrf-nodejs-example.js
// Protection SSRF Express plus axios plus ssrf-req-filter.
const express = require("express");
const axios = require("axios");
const ssrfFilter = require("ssrf-req-filter");
const app = express();
const ALLOWED_HOSTNAMES = new Set([
"api.trusted-partner.example",
"images.cdn.example",
"webhook.saas-partner.example"
]);
// Endpoint securise
app.get("/fetch-secure", async (req, res) => {
const targetUrl = req.query.url;
if (!targetUrl) return res.status(400).json({ error: "missing url" });
// Validation scheme et hostname
let parsed;
try {
parsed = new URL(targetUrl);
} catch {
return res.status(400).json({ error: "URL invalide" });
}
if (parsed.protocol !== "https:") {
return res.status(403).json({ error: "scheme non autorise" });
}
if (!ALLOWED_HOSTNAMES.has(parsed.hostname)) {
return res.status(403).json({ error: "hostname non dans allowlist" });
}
// Utilisation ssrf-req-filter pour protection IP interne
try {
const response = await axios.get(targetUrl, {
timeout: 5000,
maxRedirects: 0, // pas de suivi redirect
maxContentLength: 1048576, // 1 MB max
httpAgent: ssrfFilter("http:"),
httpsAgent: ssrfFilter("https:")
});
res.json({ status: response.status, data: String(response.data).slice(0, 500) });
} catch (err) {
console.warn(`SSRF fetch blocked or error: ${err.message}`);
res.status(500).json({ error: "fetch failed" });
}
});
app.listen(3000);Ces deux exemples couvrent les 5 couches de défense applicative : validation scheme (HTTPS only), validation hostname (allowlist stricte), résolution DNS préalable avec vérification IP (Python) ou librairie dédiée (Node.js ssrf-req-filter), pas de suivi redirect, limitation taille réponse plus timeout.
6. La défense en profondeur complète (5 couches)
| Couche | Contrôles | Outils |
|---|---|---|
| 1 — Validation applicative | Parse URL, allowlist hostnames, blocklist IPs internes, HTTPS only, pas de redirect | Python ipaddress, Node.js ssrf-req-filter, Go net/netip |
| 2 — Allowlist destinations | Deny-by-default, liste explicite des partenaires autorisés | Configuration applicative versionnée en Git |
| 3 — Configuration cloud | IMDSv2 AWS obligatoire (hop-limit 1), GCP Metadata-Flavor header, Azure IMDS header validation | AWS CLI configure, Terraform aws_instance metadata_options |
| 4 — Segmentation réseau | VPC egress filtering, Security Groups restrictifs, proxy sortant centralisé avec journalisation | AWS VPC egress rules, HAProxy proxy auth, Zscaler |
| 5 — Détection runtime | WAF SSRF signatures, SIEM monitoring requêtes sortantes anormales, SAST en pipeline | AWS WAF, Cloudflare WAF, ModSecurity CRS v4, Splunk corrélation |
Configuration AWS IMDSv2 obligatoire (Terraform) :
# terraform-aws-imdsv2-enforcement.tf
# Enforcement IMDSv2 sur instances EC2 - protection SSRF post-Capital One 2019.
resource "aws_instance" "app_server" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.medium"
metadata_options {
http_endpoint = "enabled"
http_tokens = "required" # IMDSv2 obligatoire
http_put_response_hop_limit = 1 # Protection conteneur
instance_metadata_tags = "disabled"
}
tags = {
Name = "app-server-imdsv2-enforced"
}
}
# Detection instances IMDSv1 dans le compte via AWS Config Rule
resource "aws_config_config_rule" "imdsv2_required" {
name = "ec2-imdsv2-required"
source {
owner = "AWS"
source_identifier = "EC2_IMDSV2_CHECK"
}
}7. Détection et outillage 2026
| Phase | Outils |
|---|---|
| SAST (pré-commit et CI) | Semgrep rules SSRF OWASP plus custom, SonarQube plugins, Snyk Code, Checkmarx, CodeQL, Bandit (Python), ESLint-plugin-security (JS/TS) |
| SCA | Snyk, Trivy, Grype, OSV-Scanner — détectent les lib HTTP vulnérables |
| DAST (staging) | Burp Suite Professional plus Collaborator (blind SSRF), OWASP ZAP plus plugins OAST, Nuclei SSRF templates, Acunetix |
| Runtime WAF | AWS WAF managed rule groups, Cloudflare WAF, ModSecurity CRS v4 |
| SIEM / monitoring | Splunk corrélation outbound, Elastic Security, Microsoft Sentinel |
| Pentest offensif | SSRFmap, Gopherus, SSRF Sheriff |
Exemple règle Semgrep SSRF pour Python requests :
rules:
- id: python-ssrf-requests-user-input
message: >-
Requete HTTP avec URL construite depuis entree utilisateur sans validation.
Risque SSRF (CWE-918, OWASP A10:2021). Valider allowlist hostnames, scheme
HTTPS only, bloquer IPs internes (169.254/16 IMDS, RFC 1918, loopback).
severity: ERROR
languages: [python]
metadata:
cwe: "CWE-918"
owasp: "A10:2021"
references:
- "https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/"
pattern-either:
- patterns:
- pattern: requests.$METHOD($URL, ...)
- metavariable-pattern:
metavariable: $URL
patterns:
- pattern-either:
- pattern: request.args.$X
- pattern: request.json[$X]
- pattern: request.form.$X
- pattern-not-inside: |
if validate_target_url(...):
...Règle intégrable en pre-commit hook ou en pipeline GitHub Actions pour détection précoce en développement.
8. Pour aller plus loin
- Qu'est-ce que l'OWASP Top 10 : méta-article sur le Top 10, écosystème OWASP.
- OWASP Top 10 Web — introduction et vue d'ensemble 2025 : analyse des 10 catégories.
- Roadmap AppSec : parcours AppSec engineer complet.
- Roadmap API Security : spécialisation API Top 10 2023.
- Roadmap Secure Coding : développement sécurisé par langage.
- Salaire AppSec engineer : fourchettes France 2026.
9. Points clés à retenir
- SSRF (CWE-918, A10:2021) : serveur contraint d'effectuer une requête HTTP vers une cible choisie par l'attaquant.
- Cas canonique Capital One 2019 : SSRF → AWS IMDS → credentials IAM excessifs → S3 → 106 M dossiers, 80 M$ amende plus 250+ M$ remédiation.
- Trois types : basic (in-band), blind (out-of-band via DNS callback), second-order (URL stockée puis réutilisée).
- Cibles principales : cloud metadata (AWS IMDS 169.254.169.254, Azure, GCP), services internes (Redis, Elasticsearch, DB admin), scan ports RFC 1918, lecture file:// locaux.
- Défense en profondeur 5 couches : validation applicative, allowlist destinations, configuration cloud (IMDSv2 AWS), segmentation réseau, détection runtime.
- AWS IMDSv2 obligatoire depuis novembre 2019 (réponse post-Capital One) : Session-Oriented, hop-limit 1.
- Piège redirect :
allow_redirects=Falsesystématique — bypass n°1 des protections allowlist. - CVE récentes démontrent que SSRF reste critique 2023-2024 : CVE-2023-46805 Ivanti, CVE-2024-3400 Palo Alto, CVE-2024-21887 Ivanti.
- Stack détection 2026 : Semgrep (SAST), Burp Collaborator (DAST blind), AWS WAF (runtime), SSRFmap (offensive).
- SSRF ≠ Open Redirect ≠ CSRF — trois vulnérabilités distinctes avec remédiations différentes.
La formation OWASP Web Security Zeroday Cyber Academy couvre en profondeur SSRF avec labs reproductibles (SSRF vers IMDS en environnement AWS contrôlé, blind SSRF avec Burp Collaborator, second-order SSRF en background job), implémentation des 5 couches de défense en profondeur, règles Semgrep custom, et préparation aux certifications Burp Suite Certified Practitioner plus CSSLP plus OSWE.







