FlawOpen

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

● Capital One · CWE-918 · July 2019 · Kritis
Riset Keamanan · FlawOpen

SSRF Capital One: Pencurian Kredensial AWS IMDSv1

Pengaduan pidana federal menuduh WAF yang salah konfigurasi memungkinkan SSRF mencapai AWS IMDSv1 dan membuka kredensial peran sementara serta data S3 sekitar 106 juta orang. Wajibkan token dan batasi peran.[10], [2]

💡 Penjelasan Sederhana (ELI5)

Pengaduan pidana DOJ menuduhkan rantai seperti ini: Bayangkan resepsionis yang mengambil dokumen dari alamat yang Anda berikan. Relai permintaan dapat membuat seseorang mengarahkannya ke ruang internal. Di sana, resepsionis dapat memperoleh lencana akses sementara untuk beban kerja; lencana itu hanya membuka ruangan yang diizinkan oleh peran terkait. Kesalahannya adalah mengira hanya staf yang bisa mencapai ruang itu, padahal resepsionis dapat diarahkan ke sana.[10], [8]

Konsep Kunci & Istilah

SSRF
Server-side request forgery (CWE-918): membuat server mengirim permintaan pilihan penyerang sehingga permintaan itu membawa posisi jaringan dan kepercayaan server.[9]
Web application firewall
Penyaring di depan aplikasi web. Pengaduan pidana menuduh WAF Capital One yang salah konfigurasi dapat dibuat meneruskan permintaan ke alamat internal.[10]
IMDSv1
Saat IMDSv1 diaktifkan, permintaan metadata tidak memerlukan token. Jalur kredensial keamanan peran IAM mengembalikan kredensial sementara yang terkait dengan peran tersebut.[7], [8]
IMDSv2
IMDSv2 menggunakan token sesi yang diperoleh melalui permintaan PUT. Tetapkan HttpTokens=required untuk menolak permintaan IMDSv1 tanpa token; ini memblokir banyak relai SSRF yang hanya dapat mengirim GET biasa, tetapi bukan pertahanan SSRF yang lengkap.[7], [6]
Kredensial peran IAM
Kunci AWS sementara yang diterbitkan untuk instans. Apa pun yang boleh dilakukan peran itu juga boleh dilakukan pemegang kuncinya sampai kedaluwarsa.[8]

Pemetaan kerangka kerja

Kronologi Insiden

2019-03-22

Menurut pengaduan pidana, pada 22–23 Maret terdakwa menggunakan SSRF untuk memperoleh kredensial peran firewall dan menyalin data S3 Capital One.[10], [2]

2019-07-17

Seorang peneliti eksternal melaporkan data yang terekspos melalui program pengungkapan bertanggung jawab Capital One.[2]

2019-07-29

Capital One mengumumkan insiden dan FBI menangkap seorang tersangka di Seattle.[1]

Alur Serangan Langkah demi Langkah

Langkah 1

Firewall meneruskan permintaan rekaan

Pengaduan pidana menuduh WAF yang salah konfigurasi dapat diarahkan untuk mengirim permintaan ke alamat pilihan penyerang, termasuk alamat internal.[10]

Langkah 2

Layanan metadata menyerahkan kredensial

Menurut pengaduan, relai mencapai jalur kredensial peran IAM pada layanan metadata instans. IMDSv1 tidak mewajibkan token sesi, sehingga permintaan itu dapat mengembalikan kredensial sementara untuk peran terkait.[10], [8]

Langkah 3

Peran boleh membaca terlalu banyak

Pengaduan menuduh kredensial tersebut memiliki izin peran, termasuk membuat daftar dan membaca bucket S3 yang berisi data pelanggan.[10], [8]

Langkah 4

Data keluar besar-besaran

Data pengajuan kartu kredit dari 2005 hingga awal 2019 tersalin: sekitar 100 juta orang di AS dan 6 juta di Kanada, termasuk sekitar 140.000 nomor Jaminan Sosial AS dan 80.000 nomor rekening bank terkait.[2]

Kode Sumber: Rentan vs Aman

Kode Flask ini bersifat ilustratif, bukan konfigurasi Capital One. Allowlist host adalah kontrol utama aplikasi. Proksi egress wajib menerapkan kebijakan pada alamat hasil resolusi saat koneksi, menolak rentang privat dan link-local termasuk layanan metadata instans, serta mengulangi pemeriksaan untuk setiap permintaan dan percobaan ulang; pastikan perilaku itu dalam konfigurasi proksi. Pengalihan tidak diikuti. Pemeriksaan DNS terpisah sebelum koneksi akan menyisakan kondisi balapan DNS rebinding.[9], [5], [6]

IMPLEMENTASI RENTAN
import requests
from flask import Flask, request

app = Flask(__name__)


@app.get("/fetch")
def fetch():
    # Server mengambil URL apa pun yang diberikan pemanggil.
    url = request.args["target"]
    resp = requests.get(url, timeout=5)
    return resp.text
PERBAIKAN AMAN & KUAT
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"
# Konfigurasikan proksi ini untuk menerapkan kebijakan tujuan setelah resolusi 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

Daftar Periksa Penguatan Sistem Rekayasa

Sumber