FlawOpen

flawopen.com/Incidents/capital-one-ssrf-imds-july-2019

● Capital One · CWE-918 · July 2019 · Critique
Recherche · FlawOpen

Capital One : SSRF et vol d’identifiants IMDSv1

La plainte fédérale alléguait qu’un WAF mal configuré permettait un SSRF vers IMDSv1 et exposait des identifiants temporaires et des données S3 concernant environ 106 millions de personnes. Exigez les jetons et limitez les rôles.[10], [2]

💡 Explication en Langage Simple (ELI5)

La plainte pénale du DOJ alléguait une chaîne de ce type : Imaginez une réceptionniste qui récupère des documents aux adresses que vous lui indiquez. Un relais de requêtes peut permettre à quelqu’un de l’orienter vers un bureau interne. Elle peut alors obtenir un badge temporaire pour la charge de travail ; ce badge n’ouvre que les pièces autorisées par le rôle associé. L’erreur consistait à supposer que seul le personnel pouvait atteindre le bureau, alors que la réceptionniste pouvait y être dirigée.[10], [8]

Concepts Clés et Termes

SSRF
Falsification de requête côté serveur (CWE-918) : amener un serveur à envoyer une requête choisie par l'attaquant, qui hérite alors de la position réseau et de la confiance du serveur.[9]
Pare-feu applicatif web
Un filtre devant une application web. La plainte pénale alléguait que le WAF mal configuré de Capital One pouvait relayer des requêtes vers des adresses internes.[10]
IMDSv1
Lorsque IMDSv1 est activé, les requêtes de métadonnées n’exigent pas de jeton. Le chemin des identifiants de sécurité d’un rôle IAM renvoie les identifiants temporaires associés à ce rôle.[7], [8]
IMDSv2
IMDSv2 utilise un jeton de session obtenu par une requête PUT. Définissez HttpTokens=required pour rejeter les requêtes IMDSv1 sans jeton ; cela bloque de nombreux relais SSRF limités à un GET simple, sans constituer une défense SSRF complète.[7], [6]
Identifiants de rôle IAM
Des clés AWS temporaires délivrées à une instance. Tout ce que le rôle peut faire, le détenteur des clés peut le faire aussi jusqu'à leur expiration.[8]

Correspondances de référentiels

  • Référence CWE pour la faiblesse décrite dans ce guide : CWE-918
  • D3-OTF Filtrage du trafic sortant — La restriction du trafic sortant décrite dans ce guide limite les destinations accessibles au service ou à l’hôte.

Chronologie de l'Incident

2019-03-22

Selon la plainte pénale, les 22 et 23 mars, l’intruse a utilisé le SSRF pour obtenir les identifiants du rôle du pare-feu et copier des données S3 de Capital One.[10], [2]

2019-07-17

Un chercheur externe signale les données exposées via le programme de divulgation responsable de Capital One.[2]

2019-07-29

Capital One annonce l'incident et le FBI arrête une suspecte à Seattle.[1]

Déroulement de l'Attaque Étape par Étape

Étape 1

Le pare-feu relaie une requête forgée

La plainte pénale allègue que le WAF mal configuré pouvait être amené à envoyer des requêtes vers des adresses choisies par l’attaquante, y compris internes.[10]

Étape 2

Le service de métadonnées livre les identifiants

Selon la plainte, le relais a atteint le chemin des identifiants du rôle IAM dans le service de métadonnées de l’instance. IMDSv1 n’exigeant pas de jeton de session, cette requête pouvait renvoyer les identifiants temporaires du rôle associé.[10], [8]

Étape 3

Le rôle peut lire beaucoup trop

La plainte allègue que ces identifiants portaient les permissions du rôle, notamment la liste et la lecture de buckets S3 contenant des données clients.[10], [8]

Étape 4

Les données partent en masse

Des données de demandes de carte de crédit de 2005 à début 2019 ont été copiées : environ 100 millions de personnes aux États-Unis et 6 millions au Canada, dont environ 140 000 numéros de sécurité sociale américains et 80 000 numéros de comptes bancaires associés.[2]

Code Source : Vulnérable vs Sécurisé

Code Flask illustratif, et non la configuration de Capital One. La liste d’hôtes autorisés est le principal contrôle applicatif. Le proxy de sortie obligatoire doit appliquer la politique à l’adresse résolue au moment de la connexion, bloquer les plages privées et link-local, y compris le service de métadonnées, et répéter ces contrôles pour chaque requête et nouvelle tentative ; vérifiez ce comportement dans sa configuration. Les redirections ne sont pas suivies. Une résolution séparée avant la connexion laisserait une course liée au DNS rebinding.[9], [5], [6]

IMPLÉMENTATION VULNÉRABLE
import requests
from flask import Flask, request

app = Flask(__name__)


@app.get("/fetch")
def fetch():
    # Le serveur récupère n’importe quelle URL fournie par l’appelant.
    url = request.args["target"]
    resp = requests.get(url, timeout=5)
    return resp.text
PATCH SÉCURISÉ ET ROBUSTE
from urllib.parse import urlsplit

import requests
from flask import Flask, abort, request

app = Flask(__name__)
ALLOWED_HOSTS = {"images.example-cdn.com", "api.partner.example"}



EGRESS_PROXY = "http://egress-proxy.internal:3128"
# Configurez ce proxy pour appliquer la politique de destination après la résolution DNS.

@app.get("/fetch")
def fetch():
    parts = urlsplit(request.args.get("target", ""))
    if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
        abort(400, "destination not allowed")
    if parts.port not in (None, 443) or parts.username or parts.password:
        abort(400, "destination not allowed")
    resp = requests.get(parts.geturl(), timeout=(2, 5), allow_redirects=False, proxies={"https": EGRESS_PROXY}, trust_env=False)
    if resp.is_redirect:
        abort(502, "upstream redirect refused")
    return resp.text

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources