SSRF'yi Kapatmak İçin Yalnızca Allowlist Yeterli mi?
Allowlist, SSRF riskini azaltan değerli bir kontroldür. Fakat parser farkları, DNS değişimi, redirect zincirleri ve kontrolsüz egress nedeniyle tek başına güvenlik sınırı değildir.
SECNODEX Güvenlik Ekibi
Ofansif Güvenlik
Bir uygulamada kullanıcıdan alınan URL ile PDF önizlemesi üretiliyor. Developer ekip SSRF riskini fark ediyor ve yalnızca şirketin kullandığı storage domain'ine izin veren bir allowlist ekliyor. Security ticket kapanıyor.
Kontrol ilk bakışta doğrudur. Uygulama artık rastgele bir hostname kabul etmiyor.
Fakat cevaplanmamış sorular vardır:
- Hostname comparison tam eşleşme mi, suffix kontrolü mü?
- URL'yi validation katmanı ile HTTP client aynı biçimde mi yorumluyor?
- Allowlist'teki domain bağlantı anında hangi IPv4 ve IPv6 adreslerine çözülüyor?
- DNS cevabı validation ile connection arasında değişebilir mi?
- HTTP client redirect'i otomatik takip ediyor mu?
- Redirect'in ikinci hedefi de aynı kontrollerden geçiyor mu?
- Allowlist'teki serviste open redirect varsa ne oluyor?
- İstek environment proxy üzerinden farklı bir rotaya çıkabiliyor mu?
- Application pod'u cloud metadata, Kubernetes control plane veya internal admin panellerine ağ seviyesinde erişebiliyor mu?
- Response header ve body için süre ve boyut limitleri var mı?
- Kullanıcının verdiği
Authorization,Cookieveya custom header downstream hedefe taşınıyor mu?
Bu soruların birine bile cevap verilemiyorsa “SSRF kapatıldı” demek erkendir.
Kısa cevap şu: Allowlist gereklidir, fakat yalnızca belirli bir threat model içinde anlamlıdır ve tek başına yeterli değildir. Hedeflerin az, sabit, kurum tarafından kontrol edilen ve önceden bilinen servisler olduğu durumda çok güçlü bir ilk filtredir. Buna rağmen URL parsing, DNS resolution, redirect, gerçek connection destination ve network egress aynı policy'nin parçası haline getirilmezse allowlist yalnızca bir string kontrolü olarak kalır.
Üstelik bazı product requirement'larında allowlist baştan uygulanabilir değildir. Kullanıcıların kendi webhook endpoint'lerini tanımladığı, dışarıdaki herhangi bir görseli içe aktarabildiği veya arbitrary document URL'sinden preview oluşturduğu sistemlerde hedef listesi önceden bilinmez. Böyle bir tasarımda “bütün güvenilir domain'leri listeleyelim” yaklaşımı ölçeklenmez.
Bu yazıda SSRF'nin yalnızca bir input validation problemi olmadığını, allowlist'in nerede değer ürettiğini, hangi koşullarda aşıldığını ve güvenli outbound request akışının nasıl tasarlanması gerektiğini teknik ayrıntılarıyla inceleyeceğiz. Son bölümde, exact host ve IP policy kullanan, redirect takip etmeyen, DNS validation ile connection arasındaki boşluğu kapatan ve response limitleri uygulayan test edilmiş bir Node.js örneği de bulunuyor.
SSRF tam olarak nedir?
SSRF, attacker'ın uygulamayı kendi seçtiği veya etkileyebildiği bir hedefe server-side request göndermek için kullanmasıdır. Attacker hedefe doğrudan erişemese bile vulnerable application'ın network position'ından, identity'sinden ve protocol capability'sinden yararlanır.
Basit akış şöyledir:
- 1Kullanıcı bir URL, hostname, IP, path veya downstream request'i etkileyen başka bir değer gönderir.
- 2Application bu değeri kullanarak server-side request oluşturur.
- 3Request, application'ın erişebildiği bir hedefe gider.
- 4Attacker response'u doğrudan görür, response özelliklerinden sonuç çıkarır veya request'in yan etkisinden yararlanır.
Risk yalnızca GET http://... çağrısıyla sınırlı değildir. OWASP SSRF Prevention Cheat Sheet, ilk request HTTP ile gelse bile ikinci request'in FTP, SMB, SMTP gibi başka protokollere veya file, gopher, data benzeri scheme'lere yönelebileceğine dikkat çeker. Gerçek capability kullanılan library, protocol handler ve deployment ortamına bağlıdır.
SSRF iki ana biçimde görülür.
Response görülebilen SSRF
Application downstream response'u kullanıcıya geri döndürür. Attacker internal API response'u, file içeriğini, metadata cevabını veya hata mesajını doğrudan görebilir. Exploit validation'ı görece kolaydır.
Blind SSRF
Application response'u kullanıcıya vermez. Ancak outbound request yine gerçekleşir. Attacker kendi kontrollü endpoint'indeki DNS veya HTTP log'larından callback'i görebilir. Timing difference, status-dependent behavior, asynchronous job sonucu veya downstream side effect de signal üretebilir.
Blind olması riskin düşük olduğu anlamına gelmez. Internal port discovery, request smuggling zinciri, credential exposure, webhook abuse ve cloud service action'ları response body olmadan da mümkün olabilir.
SSRF'nin etkisi neden application'dan application'a değişir?
Aynı code weakness iki farklı deployment'ta tamamen farklı sonuç üretir.
Bir marketing image resizer yalnızca internet egress'e sahip, metadata endpoint'e route edemiyor ve hiçbir credential taşımıyorsa impact sınırlı kalabilir. Aynı fetch code'u yüksek yetkili service account ile çalışan, internal service mesh'e bağlı, cloud metadata'ya erişebilen bir worker içinde çalışıyorsa sonuç çok daha ağır olabilir.
Gerçek impact şu bileşenlerin birleşimidir:
| Bileşen | Sorulması gereken soru |
|---|---|
| Input control | Attacker URL'nin tamamını mı, yalnızca path'i mi, yoksa bir resource ID'yi mi kontrol ediyor? |
| Protocol capability | Client sadece HTTPS mi konuşuyor, başka scheme veya protocol handler'lar var mı? |
| Network reachability | Process hangi internal subnet, control plane ve link-local endpoint'lere erişebiliyor? |
| Runtime identity | Request sırasında hangi cloud role, service account, mTLS identity veya token kullanılabiliyor? |
| Header behavior | Authorization, Cookie, trace header veya internal header otomatik ekleniyor mu? |
| Response exposure | Body, status, header, timing veya error kullanıcıya yansıyor mu? |
| Side effect | Downstream hedef yalnızca read mi yapıyor, state değiştiren endpoint'ler de erişilebilir mi? |
| Observability | DNS query ve outbound connection kayda giriyor mu? |
Bu nedenle SSRF severity'si yalnızca payload'a bakılarak atanamaz. Application code, runtime configuration, egress topology ve identity context birlikte incelenmelidir.
Allowlist SSRF'ye karşı neyi çözer?
Doğru kurulan allowlist, outbound request'in yalnızca önceden onaylanmış değerlerle oluşturulmasını sağlar. “Nereye request gönderilebilir?” sorusunun cevabı küçük ve deterministikse attack surface ciddi biçimde daralır.
Örneğin bir application yalnızca aşağıdaki iki backend ile konuşacaksa kullanıcıdan tam URL almak gereksizdir:
https://document-api.corp.example:443https://image-api.corp.example:443
Kullanıcının seçebileceği değer document veya image gibi business identifier olabilir. Server bu identifier'ı kendi configuration'ındaki sabit origin'e map eder. Kullanıcı hostname, scheme ve port üretmez.
Bu modelde allowlist güçlüdür çünkü attacker'ın network destination üzerindeki kontrolü daha input oluşurken kaldırılmıştır.
En güvenli sıralama genellikle şöyledir:
- 1Kullanıcı network location vermez, yalnızca resource ID verir.
- 2Server ID'yi sabit ve güvenilir bir destination'a map eder.
- 3Scheme, host ve port configuration'dan gelir.
- 4Kullanıcı yalnızca dar biçimde tanımlanmış path segmenti veya business data sağlayabilir.
- 5Request path'i de endpoint template ve type-safe parameter ile oluşturulur.
Burada kritik ayrım şudur:
Kısa cevap
Full URL allowlist ile resource allowlist aynı güvenlik kalitesine sahip değildir.
https://trusted.example/... biçimindeki tam URL'yi parse edip hostname kontrol etmek hâlâ karmaşık bir URL güvenlik problemidir. report_id=7821 alıp server-side configuration üzerinden sabit bir endpoint'e gitmek ise kullanıcının destination seçmesini baştan engeller.
Allowlist hangi senaryoda gerçekten uygulanabilir?
OWASP SSRF Prevention Cheat Sheet iki ayrı durum tanımlar:
- 1Application yalnızca kimliği belli, güvenilir application'lara request gönderebilir.
- 2Application dışarıdaki herhangi bir domain veya IP adresine request göndermek zorundadır.
Birinci durumda allowlist kullanılabilir. İkinci durumda hedefler önceden bilinmediği için klasik domain allowlist iş ihtiyacını karşılamaz.
Bu ayrım tasarımın başlangıç noktası olmalıdır.
Hedeflerin sabit olduğu durum
Örnekler:
- Bir ödeme orchestration servisinin iki payment provider API'sine bağlanması
- Bir HR uygulamasının tek bir internal employee API'sine veri göndermesi
- Bir media worker'ın yalnızca kurumun object storage endpoint'inden dosya çekmesi
- Bir reporting servisinin belirli analytics backend'lerine erişmesi
Bu modelde exact hostname, scheme, port ve beklenen IP seti tanımlanabilir. Network egress de aynı hedeflerle sınırlanabilir.
Hedeflerin müşteri tarafından belirlendiği durum
Örnekler:
- SaaS müşterisinin kendi webhook URL'sini tanımlaması
- Kullanıcının herhangi bir public image URL'sini import etmesi
- Link preview veya screenshot servisinin public web'e erişmesi
- Security scanner'ın müşteriye ait internet-facing hedefi çağırması
Burada her meşru domain'i önceden allowlist'e eklemek mümkün değildir. Çözüm, request yapan component'i ayrı bir fetcher service'e taşımak, internal route'ları network seviyesinde kapatmak, yalnızca public destination'lara izin vermek ve her connection'da destination'ı yeniden doğrulamaktır.
Hedefin internal fakat dinamik olduğu durum
Service discovery kullanılan microservice ortamlarında hostname sabit görünse bile arkasındaki IP seti deployment boyunca değişebilir. Internal request zaten tasarımın parçasıdır. Burada “private IP'leri block et” kuralı doğrudan uygulanamaz.
Çözüm endpoint identity, service account, mTLS, service mesh authorization ve egress policy ile iş akışını tanımlamaktır. Kullanıcıdan alınan arbitrary service name'i cluster DNS'e göndermek güvenli değildir.
Neden yalnızca hostname allowlist yeterli değildir?
Çünkü outbound request tek aşamalı bir işlem değildir.
Bir URL önce string olarak gelir. Sonra parse edilir. Host normalize edilir. DNS resolution gerçekleşir. HTTP client connection açar. TLS handshake yapar. Request gönderilir. Response redirect döndürebilir. Client yeni URL'yi parse edip ikinci connection'ı açabilir. Proxy, service mesh veya runtime configuration rotayı değiştirebilir.
Allowlist bu zincirin yalnızca ilk kısmını kontrol ediyorsa geri kalan kararlar farklı sonuç üretebilir.
Güvenli model şu invariant'ı korumalıdır:
Kısa cevap
Validation sırasında onaylanan logical destination ile connection sırasında ulaşılan network destination aynı policy kapsamında olmalıdır.
Bu invariant'ın bozulduğu başlıca noktaları tek tek inceleyelim.
1. Hatalı string comparison allowlist'i görünüşte var eder
En basit hata host'u tam eşleşmeyle karşılaştırmamaktır.
Riskli örnekler:
host.includes("trusted.example")
host.endsWith("trusted.example")
url.startsWith("https://trusted.example")İlk kontrol trusted.example.attacker.invalid gibi bir hostname'i kabul edebilir. İkinci kontrol dot boundary düşünülmeden yazılırsa nottrusted.example benzeri sonuçlar doğurabilir. Üçüncü kontrol userinfo, port veya parsing farklılıklarını dikkate almaz.
Güvenli comparison kararı raw URL string'i üzerinde değil, standart parser'ın ürettiği canonical host değeri üzerinde verilmelidir.
Exact host bekleniyorsa:
host === "trusted.example"Subdomain'lere gerçekten izin verilecekse iki koşul birlikte değerlendirilmelidir:
host === "trusted.example"
host.endsWith(".trusted.example")Fakat wildcard veya suffix allowlist, o namespace altındaki bütün subdomain'leri güvenilir sayar. Bu karar ancak subdomain lifecycle, DNS ownership, third-party delegation ve dangling record yönetimi güvence altındaysa alınmalıdır. Bir SaaS vendor'a CNAME edilen veya artık kullanılmayan subdomain allowlist'in içine sessizce risk taşıyabilir.
En güvenli yaklaşım, ihtiyaç duyulan host'ları tek tek exact allowlist'e almaktır.
2. URL parsing bir regex problemi değildir
URL, scheme, authority, userinfo, host, port, path, query ve fragment gibi bileşenlerden oluşur. RFC 3986 ortak syntax'ı tanımlar, fakat application stack içindeki parser'lar, reverse proxy'ler ve client library'ler edge case'leri aynı biçimde ele almayabilir.
Şu değer bir insan gözüne trusted.example ile ilişkili görünebilir:
https://trusted.example@attacker.invalid/documentStandart URL yorumunda trusted.example userinfo, gerçek host ise attacker.invalid olur.
Benzer şekilde percent encoding, backslash davranışı, multiple @ character, bracket, Unicode normalization, trailing dot, encoded delimiter ve control character edge case'leri custom regex'leri kırılgan hale getirir.
Güvenli parser kullanmanın yanında aşağıdaki kurallar da açıkça belirlenmelidir:
- URL absolute ve hierarchical olmalı
- Scheme yalnızca gerekli değerlerden biri olmalı
- Userinfo reddedilmeli
- Host boş olmamalı
- Port parse edilmeli ve izin verilen setle karşılaştırılmalı
- Fragment server-side request için gereksizse reddedilmeli
- Host parser output'undan alınmalı
- Canonicalization yalnızca bir kez ve tanımlı sırada yapılmalı
- Validation sonrasında raw string başka bir parser'a verilmemeli
“Önce regex ile temizleyip sonra URL parser'a gönderelim” yaklaşımı da sorun çıkarabilir. Validation bir representation üzerinde, connection başka representation üzerinde gerçekleşir.
3. Canonicalization yapılmadan aynı adres farklı görünebilir
Hostname'ler case-insensitive yorumlanır. Fully qualified domain name sonunda dot bulunabilir. Internationalized domain name parser tarafından Punycode'a çevrilebilir. IPv6 adresleri farklı ama eşdeğer textual representation'lara sahip olabilir. IPv4-mapped IPv6 adresleri IPv4 destination'ı IPv6 biçiminde gösterebilir.
Bu nedenle comparison öncesinde canonical representation gerekir.
Örnek policy:
- Scheme lower-case hale getirilir
- Host, aynı URL parser'ın ürettiği hostname üzerinden alınır
- DNS name lower-case hale getirilir
- Tek trailing dot kararı net biçimde uygulanır
- IDN destekleniyorsa aynı IDNA implementation'ı hem configuration hem input tarafında kullanılır
- IP address string olarak elle dönüştürülmez, platformun IP parser'ı kullanılır
- IPv4 ve IPv6 ayrı ayrı değerlendirilir
- IPv4-mapped IPv6 davranışı test edilir
Burada “normalize edelim” ifadesi sınırsız rewriting anlamına gelmez. Birden fazla trailing dot, invalid label veya beklenmeyen Unicode karakteri düzeltmeye çalışmak yerine fail closed davranmak daha güvenlidir.
IDN ihtiyacı olmayan internal fetcher'da yalnızca önceden normalize edilmiş ASCII hostname kabul etmek attack surface'i azaltır. IDN gerçekten gerekiyorsa Punycode conversion elle yazılmamalı, kullanılan runtime'ın iyi test edilmiş implementation'ı merkezi biçimde kullanılmalıdır.
4. Scheme ve port allowlist'in parçası değilse hedef hâlâ fazla geniştir
Bir hostname'e izin vermek, o host üzerindeki bütün servislerin eşit derecede güvenilir olduğu anlamına gelmez.
Şunlar farklı destination'lardır:
https://api.example:443
http://api.example:80
http://api.example:8080
https://api.example:8443Aynı IP üzerinde public API, internal admin interface, metrics endpoint ve development service farklı port'larda çalışabilir. Hostname allowlist doğru olsa bile port serbest bırakılırsa attacker beklenmeyen service'e ulaşabilir.
Scheme farkı da önemlidir. HTTPS beklenen akışta HTTP'ye izin vermek transport security'yi kaldırır. Bazı library'lerde URL scheme başka protocol handler'lara yönlenebilir. SSRF yalnızca HTTP problemi olmadığı için “host doğruysa scheme önemli değil” yaklaşımı hatalıdır.
Policy en az şu tuple'ı tanımlamalıdır:
scheme + canonical host + portGerekiyorsa method ve path prefix de eklenir:
GET + https + document-api.example + 443 + /v1/files/Bu, full URL string allowlist'i kullanmak anlamına gelmez. Her component parser output'u üzerinden ayrı ayrı doğrulanır.
5. DNS cevabı allowlist'teki domain'i internal IP'ye taşıyabilir
Hostname kontrolü yapıldıktan sonra gerçek connection IP adresine gider. Domain allowlist'te olsa bile DNS sonucu beklenmeyen bir address olabilir.
Risk birkaç biçimde oluşur:
- Domain attacker tarafından kontrol ediliyordur
- Allowlist'e yanlışlıkla third-party veya customer-controlled domain eklenmiştir
- DNS record compromise edilmiştir
- Split-horizon DNS farklı network'te farklı cevap döndürür
- DNS search path beklenmeyen isim çözümlemesi yapar
- CNAME chain kurumun kontrol etmediği bir namespace'e gider
- Bir answer public, başka bir answer private veya link-local olabilir
- Record validation anında farklı, connection anında farklı cevap verebilir
DNS rebinding'in temel fikri, aynı hostname'in zaman içinde farklı IP'lere çözülmesidir. Application önce güvenli görünen public address'i doğrular. HTTP client daha sonra hostname'i yeniden çözdüğünde internal address alabilir.
Yalnızca şu akış yeterli değildir:
1. hostname allowlist'te mi?
2. DNS çöz
3. IP public mi?
4. URL'yi normal HTTP client'a verSon adımda client hostname'i tekrar çözerse ikinci DNS query ilk doğrulamanın sonucunu geçersiz kılabilir.
Güvenli akış şudur:
1. URL'yi parse et
2. Host, scheme ve port policy'sini doğrula
3. A ve AAAA sonuçlarının tamamını çöz
4. Her address'i policy'ye göre doğrula
5. Doğrulanan address'lerden birini seç
6. Connection'ı doğrudan o address'e aç
7. HTTP Host header ve TLS SNI için original hostname'i koruBu yaklaşım DNS validation ile connection arasındaki time-of-check to time-of-use boşluğunu daraltır.
Neden bütün DNS cevapları kontrol edilmeli?
Resolver şu sonucu döndürsün:
198.51.100.10
10.20.30.40Yalnızca ilk address kontrol edilirse runtime veya connection retry ikinci address'i seçebilir. Bir answer bile policy dışındaysa fail closed davranmak daha güvenlidir.
CDN veya dynamic infrastructure yüzünden IP seti sürekli değişiyorsa exact IP allowlist operasyonel olarak zorlaşabilir. Bu durumda private ve special-purpose address'leri merkezi biçimde block eden egress proxy, DNS policy ve network firewall daha sürdürülebilir olabilir. Yine de connection anındaki address doğrulaması korunmalıdır.
6. “Private IP'leri engelledik” ifadesi IPv4 ile bitmez
En sık görülen filter yalnızca şu IPv4 range'lerini engeller:
10.0.0.0/8172.16.0.0/12192.168.0.0/16127.0.0.0/8
Bu liste SSRF için eksiktir.
Link-local, unspecified, multicast, carrier-grade NAT, benchmarking, documentation, reserved ve protocol assignment range'leri de destination policy içinde düşünülmelidir. IPv6 tarafında loopback, link-local, unique-local, multicast, unspecified, IPv4-mapped ve diğer special-purpose range'ler vardır. Cloud provider metadata için IPv4 dışında IPv6 endpoint bulunabilir. AWS, IPv6 IMDS endpoint'i olarak [fd00:ec2::254] adresini de belgelemektedir.
IANA IPv4 ve IPv6 Special-Purpose Address Registries bu alanların güncel otoritatif listesidir. Uygulama içine yıllarca güncellenmeyecek bir regex veya eksik CIDR listesi gömmek yerine şu seçenekler değerlendirilmelidir:
- Platformun güncel IP address library'si
- Merkezi egress proxy
- Network firewall policy
- Düzenli güncellenen special-purpose registry verisi
- Organization-specific internal CIDR inventory
isPrivate() benzeri bir helper'ın neyi private saydığı da doğrulanmalıdır. Bazı API'ler yalnızca RFC 1918 range'lerini kapsar. Bazıları global unicast görünen ama internet destination olarak kullanılmaması gereken address'leri farklı sınıflandırır.
7. Redirect, doğru ilk hedefi yanlış ikinci hedefe çevirebilir
Application https://trusted.example/file adresini doğrular. İlk request allowlist'teki host'a gider. Server şu response'u döndürür:
HTTP/1.1 302 Found
Location: http://169.254.169.254/...HTTP client redirect'i otomatik takip ediyorsa ikinci request validation'dan geçmeden link-local hedefe gidebilir.
OWASP SSRF Prevention Cheat Sheet bu nedenle client'ta redirect takibinin kapatılmasını önerir.
Redirect gerçekten business requirement ise her hop yeni bir request gibi ele alınmalıdır:
- 1
Locationdeğeri current URL'ye göre resolve edilir - 2Yeni URL yeniden parse edilir
- 3Scheme, host ve port policy'si yeniden uygulanır
- 4DNS yeniden çözülür
- 5Bütün address'ler yeniden doğrulanır
- 6Connection doğrulanan IP'ye pin edilir
- 7Hop sayısı düşük bir limit ile sınırlandırılır
- 8Loop detection uygulanır
- 9Sensitive header'lar yeni origin'e taşınmaz
“İlk host allowlist'teydi, redirect güvenilirdir” varsayımı doğru değildir.
Allowlist'teki host open redirect içeriyorsa
Attacker'ın allowlist'i bypass etmesi için DNS'i kontrol etmesi gerekmez. Trusted host üzerindeki open redirect endpoint'i yeterli olabilir:
https://trusted.example/redirect?next=http://internal-admin/Bu nedenle allowlist'e alınan service'in kendi vulnerability'leri de threat model'in parçasıdır. Mümkünse origin'in tamamına değil gerekli path prefix'ine izin verilmeli, fakat path normalization ve redirect davranışı yine test edilmelidir.
8. Allowlist'teki domain her zaman güvenilir kalmaz
Allowlist çoğu kurumda configuration dosyasına bir kez yazılır ve unutulur. Oysa domain ownership ve routing zaman içinde değişir.
Risk kaynakları:
- Domain expiration
- Subdomain takeover
- Dangling DNS record
- CDN veya SaaS tenant mapping'inin boşa düşmesi
- Vendor acquisition veya infrastructure değişimi
- DNS account compromise
- Certificate issuance problemi
- CNAME target ownership değişimi
- Geçici test domain'inin production allowlist'te kalması
Bu nedenle allowlist item yalnızca string değil, lifecycle sahibi bir asset olmalıdır.
Her item için şu metadata tutulabilir:
| Alan | Örnek |
|---|---|
| Owner | Document Platform Team |
| Business purpose | PDF import |
| Allowed origin | https://files.vendor.example:443 |
| Expected IP veya network | Vendor tarafından yayınlanan range |
| Path scope | /api/v2/documents/ |
| Added date | 2026-07-23 |
| Review date | 2026-10-23 |
| Redirect policy | Disabled |
| Authentication | Dedicated scoped token |
| Data classification | Internal |
| Expiry condition | Vendor contract end |
DNS ve ownership değişiklikleri izlenmiyorsa allowlist zamanla güvenilirlik kaybeder.
9. Proxy ve runtime ayarları request rotasını değiştirebilir
Birçok HTTP client HTTP_PROXY, HTTPS_PROXY ve NO_PROXY gibi environment variable'ları kullanabilir. Bazı framework ve enterprise runtime'lar outbound request'i transparent proxy veya service mesh üzerinden geçirir.
Bu yapı başlı başına kötü değildir. Merkezi egress proxy SSRF savunmasının en güçlü parçalarından biri olabilir. Fakat application validation ile gerçek network route arasında tutarlılık bulunmalıdır.
Sorulması gerekenler:
- Client environment proxy'yi otomatik kullanıyor mu?
- Proxy hostname'i yeniden mi çözüyor?
- Validation application'da, DNS resolution proxy'de gerçekleşiyorsa hangi tarafın sonucu geçerli?
NO_PROXYrange'i metadata veya internal network için beklenmeyen direct route açıyor mu?- Redirect ve protocol policy proxy'de de uygulanıyor mu?
- Proxy CONNECT method'u ve destination port'ları sınırlıyor mu?
- Service mesh sidecar internal service'lere geniş erişim sağlıyor mu?
Application kendi DNS sonucuna göre IP'yi doğrulayıp proxy'ye yalnızca original hostname gönderirse proxy farklı bir IP'ye bağlanabilir. Bu modelde destination validation proxy tarafında da yapılmalıdır.
Güvenli mimaride policy enforcement point net olmalıdır. Request'i gerçekten dışarı çıkaran component, final destination'ı doğrulayabilmelidir.
10. Header ve identity taşınması SSRF impact'ini büyütür
SSRF yalnızca destination kontrolü değildir. Request ile hangi authority'nin taşındığı da önemlidir.
Riskli örnekler:
- Incoming
Authorizationheader'ını downstream request'e kopyalamak - Kullanıcının custom header eklemesine izin vermek
- Internal service token'ını bütün outbound request'lere otomatik eklemek
- Cookie jar'ı farklı origin'ler arasında paylaşmak
- mTLS client certificate'i arbitrary destination'a sunmak
- Cloud SDK credential'ını fetcher process içinde erişilebilir tutmak
X-Forwarded-*veya internal routing header'larını kullanıcıya kontrol ettirmek
Güvenli fetcher, request'i sıfırdan oluşturmalıdır. Incoming request header'ları topluca forward edilmemelidir. Yalnızca use case için gerekli, sabit veya server-controlled header'lar eklenmelidir.
Authentication gerekiyorsa credential destination'a özel, minimum yetkili ve kısa ömürlü olmalıdır. Aynı process hem arbitrary public URL fetch ediyor hem yüksek yetkili internal API token'ı taşıyorsa isolation zayıftır.
11. Response limitleri olmadan SSRF kapatılmış sayılmaz
Destination güvenli olsa bile response application'ı zorlayabilir.
Bir fetcher şu kaynak tüketimi risklerini yönetmelidir:
- Bağlantı timeout
- TLS handshake timeout
- Response header timeout
- Toplam request deadline
- Maksimum response header boyutu
- Maksimum body boyutu
- Maksimum redirect sayısı
- Maksimum decompressed body boyutu
- Content-Type validation
- Streaming backpressure
- Concurrent request limiti
- Per-tenant rate limit
Content-Length tek başına güvenilir değildir. Header bulunmayabilir veya yanlış olabilir. Body okunurken byte count uygulanmalıdır.
Automatic decompression da dikkat ister. Wire üzerinde küçük görünen compressed body memory'de çok büyüyebilir. Fetcher'ın amacına göre compression kapatılabilir veya decompressed size ayrıca sınırlandırılabilir.
Document converter, image parser veya archive extractor gibi downstream component'ler de ayrı attack surface'tir. SSRF filter doğru çalışsa bile malicious file parser vulnerability veya decompression bomb riski devam eder.
Allowlist'in tek başına kaçırdığı threat class'larının özeti
| Threat class | Allowlist neden yetmez? | Ek kontrol |
|---|---|---|
| Parser confusion | Raw string ile gerçek host farklı yorumlanabilir | Tek parser, component-based validation |
| Canonicalization | Aynı host farklı biçimlerde temsil edilebilir | Canonical host ve IP parsing |
| DNS rebinding | Host validation sonrası farklı IP'ye çözülebilir | Bütün DNS answer'larını doğrulama ve IP pinning |
| Mixed DNS answer | Bir address güvenli, diğeri internal olabilir | Bir answer policy dışıysa fail closed |
| Redirect | İlk hedef doğru, sonraki hedef yasak olabilir | Redirect kapatma veya her hop'u yeniden doğrulama |
| Scheme ve port | Allowlisted host üzerinde farklı service'e gidilebilir | Scheme, port ve gerekirse method policy |
| Trusted host weakness | Open redirect veya takeover yeni route açabilir | Asset lifecycle ve path scope |
| Proxy difference | Application ile proxy farklı destination görebilir | Enforcement'ı gerçek egress noktasında yapmak |
| Credential forwarding | Request authority yanlış hedefe taşınabilir | Header allowlist ve scoped identity |
| Resource exhaustion | Güvenli hedef aşırı response üretebilir | Timeout, size ve concurrency limitleri |
| Network reachability | Application internal control plane'e ulaşabilir | Egress firewall ve isolation |
| Blind SSRF | Response görünmese de callback veya side effect oluşur | DNS ve egress telemetry |
SSRF için doğru savunma mimarisi nasıl kurulmalı?
SSRF'yi tek bir helper function ile tamamen çözmeye çalışmak doğru hedef değildir. Savunma, application design ile network enforcement arasında birkaç katmana ayrılmalıdır.
Katman 1: Arbitrary URL ihtiyacını kaldırın
İlk soru “URL'yi nasıl validate ederiz?” değil, “Kullanıcı neden URL verebiliyor?” olmalıdır.
Kullanıcı belirli bir vendor veya internal service içindeki resource'u seçiyorsa tam URL yerine şu değerlerden biri alınabilir:
- Resource ID
- Provider ID
- Predefined destination key
- Signed object reference
- Server-generated callback token
- Database'deki onaylı endpoint kaydı
Örnek:
{
"provider": "document_store",
"document_id": "doc_7x91"
}Server kendi configuration'ından aşağıdaki request'i üretir:
GET https://document-api.corp.example/v1/documents/doc_7x91Bu model user-controlled hostname'i tamamen ortadan kaldırır. document_id de beklenen formatta validate edilir ve path segmenti olarak doğru encode edilir.
Katman 2: Destination policy'yi component bazında uygulayın
Full URL kabul edilmesi zorunluysa policy şu parçaları ayrı ayrı değerlendirmelidir:
- Scheme
- Canonical hostname
- Explicit veya default port
- DNS answer seti
- Path scope
- Method
- Header seti
- Redirect behavior
- Response type
Policy merkezi bir module veya egress service içinde tutulmalıdır. Her development team'in kendi regex'ini yazması aynı vulnerability class'ının farklı biçimlerde geri gelmesine yol açar.
Katman 3: Connection destination'ı doğrulayın
Hostname allowlist'ten sonra A ve AAAA answer'larının tamamı kontrol edilmelidir.
Hedefler sabitse en güçlü model exact expected IP veya provider'ın kontrollü CIDR setidir. IP'ler çok dinamikse en azından şu gruplar block edilmelidir:
- Organization internal CIDR'ları
- Loopback
- Link-local
- Unique-local IPv6
- Unspecified address
- Multicast
- Cloud metadata address'leri
- Cluster service CIDR'ları
- Node ve pod CIDR'ları
- Management network'leri
- Special-purpose address space
Kontrolün ardından connection doğrulanan address'e açılmalıdır. HTTP Host header ile TLS Server Name Indication original hostname'i kullanmaya devam etmelidir. Aksi halde virtual hosting ve certificate validation bozulur.
Katman 4: Redirect'i kapatın
Business requirement yoksa en güvenli seçenek redirect'i takip etmemektir.
Gerekiyorsa redirect handler default client behavior'ına bırakılmamalıdır. Her hop aynı validation pipeline'ından geçmeli, maksimum hop sayısı ve origin değişimi policy ile sınırlandırılmalıdır.
Katman 5: Egress'i network seviyesinde sınırlandırın
Application layer kontrolü bug içerebilir. Library update parser davranışını değiştirebilir. Yeni code path güvenli helper'ı kullanmadan request gönderebilir. Bu nedenle network egress ikinci ve bağımsız bir security boundary olmalıdır.
Sabit backend'lerle konuşan application için ideal egress policy yalnızca gereken destination ve port'lara izin verir. Public web'e çıkması gereken fetcher ise internal route'lara ulaşamayan ayrı network segment'inde çalışır.
Kubernetes ortamında NetworkPolicy egress kuralı kullanılabilir, fakat kullanılan CNI implementation'ının egress enforcement desteklediği doğrulanmalıdır. NetworkPolicy nesnesinin cluster'da bulunması trafiğin gerçekten engellendiğini tek başına kanıtlamaz.
Cloud firewall, host firewall, service mesh egress gateway veya dedicated proxy kullanılabilir. Önemli olan, bypass edilemeyen gerçek data path üzerinde enforcement yapılmasıdır.
Katman 6: Fetcher'ı ayrı ve düşük yetkili bir component yapın
Arbitrary public URL fetch etmek zorunda olan feature'ları ana application process'inden ayırmak güçlü bir tasarım tercihidir.
Dedicated fetcher:
- Internal DNS zone'larını çözemez
- Internal subnet'lere route edemez
- Cloud metadata'ya erişemez
- Production database credential'ı taşımaz
- Service mesh içindeki bütün backend'lere identity sunmaz
- Yalnızca gerekli HTTP method'larını kullanır
- Sıkı timeout ve body limitleri uygular
- Response'u güvenli bir object store'a yazar
- Request ve connection telemetry üretir
Ana application fetcher'a URL yanında tenant ID, expected content type ve maximum size gibi policy parameter'ları gönderir. Fetcher client'ın verdiği limitleri körlemesine yükseltmez, server-side üst sınırlar uygular.
Bu separation, application layer bypass gerçekleşse bile blast radius'i küçültür.
Katman 7: Observability ve detection kurun
SSRF denemeleri çoğu zaman error üretir. Bu error'lar yalnızca generic application log'una bırakılmamalıdır.
Kaydedilebilecek alanlar:
- Request correlation ID
- Tenant veya account ID
- Feature adı
- Canonical hostname
- Scheme ve port
- DNS answer'larının tamamı
- Seçilen connection IP
- Redirect location ve hop sayısı
- Policy decision
- Rejection reason
- Response status
- Response size
- Duration
- Timeout türü
- Egress proxy decision
Sensitive query string, credential veya personal data log'a yazılmamalıdır. URL gerektiğinde query'siz veya redacted biçimde kaydedilmelidir.
Alert örnekleri:
- Link-local veya loopback destination denemesi
- Aynı tenant'tan art arda çok sayıda farklı port
- Allowlisted host'un internal address'e çözülmesi
- Yüksek sayıda DNS failure
- Çok sayıda redirect rejection
- Metadata hostname veya IP denemesi
- Beklenmeyen IPv6 destination
- Fetcher'ın egress firewall tarafından sık engellenmesi
- Normalde sabit olan domain'in IP setinde ani değişiklik
Cloud metadata neden SSRF tartışmasının merkezindedir?
Cloud instance metadata service, workload'un çalıştığı instance veya VM hakkında bilgi sağlar. Bazı ortamlarda identity token veya credential erişimiyle de ilişkilidir. Endpoint genellikle workload içinden erişilebilir, internetten doğrudan erişilemez.
Bu özellik SSRF için cazip bir target oluşturur. Vulnerable application dışarıdan gelen URL'yi server-side çağırdığında attacker application'ın network position'ını kullanabilir.
AWS IMDS
AWS EC2 Instance Metadata Service IPv4 için 169.254.169.254 adresini kullanır. AWS ayrıca Nitro tabanlı ve IPv6 destekli ortamlarda [fd00:ec2::254] endpoint'ini belgelemektedir.
IMDSv2 token gerektirir. Token almak için önce PUT request'i, ardından token header'ı içeren metadata request'i gerekir. Bu mekanizma birçok klasik SSRF primitive'ine karşı önemli defense in depth sağlar. Ancak IMDSv2, application'ın arbitrary request oluşturabilmesi sorununu ortadan kaldırmaz.
Şu nedenlerle uygulama katmanı ve egress kontrolü yine gereklidir:
- Vulnerable client method ve custom header kontrolüne izin veriyor olabilir
- Attacker internal service'leri hedefleyebilir
- Metadata dışındaki control plane'ler erişilebilir olabilir
- Workload başka cloud veya on-premise ortamında çalışabilir
- Future configuration değişiklikleri riski geri getirebilir
- Fetcher IPv6 endpoint'e erişebilir
Mümkünse IMDS tamamen devre dışı bırakılmalı veya IMDSv2 zorunlu hale getirilmelidir. Workload identity ve credential permission minimum tutulmalıdır.
Google Cloud metadata
Google Cloud Compute Engine metadata server için request'lerde Metadata-Flavor: Google header'ı kullanılır. Bu header requirement önemli bir korumadır, fakat custom header eklenebilen SSRF primitive'inde tek başına yeterli kabul edilmemelidir.
Google'ın resmî belgeleri metadata server'a metadata.google.internal hostname'i veya link-local address üzerinden erişim örnekleri sunar. Bu nedenle yalnızca bir hostname'i veya yalnızca bir IPv4 string'ini block etmek kapsamlı çözüm değildir.
Azure IMDS
Azure Instance Metadata Service de 169.254.169.254 üzerindeki well-known non-routable endpoint'i kullanır ve request'lerde Metadata: true header'ı bekler. Microsoft, bu endpoint'in yalnızca VM içinden erişilebilir olduğunu ve trafiğin host'tan ayrılmadığını açıklar.
Yine aynı sonuç geçerlidir. Header requirement risk azaltır, fakat application kullanıcı kontrollü header gönderebiliyorsa veya başka internal service'lere erişebiliyorsa SSRF kök nedeni devam eder.
Cloud metadata için doğru karar
Cloud-side hardening, SSRF remediation'ın yerine değil yanına konur:
- 1Arbitrary destination control kaldırılır
- 2Application validation uygulanır
- 3Metadata IPv4 ve IPv6 route'ları egress seviyesinde engellenir
- 4Metadata service'in en güvenli modu zorunlu tutulur
- 5Workload identity minimum permission ile çalışır
- 6Metadata access telemetry izlenir
Kubernetes ve service mesh ortamında SSRF nasıl değişir?
Container ortamında localhost yalnızca application process'i anlamına gelmeyebilir. Aynı pod içindeki sidecar, metrics endpoint veya admin interface de loopback üzerinden erişilebilir. Pod network'ü cluster service CIDR'ına, node service'lerine, DNS'e ve internal control plane'lere route sunabilir.
Service mesh ise outbound request'e workload identity ve mTLS ekleyebilir. Bu, arbitrary URL fetch eden process'in normalde dışarıdan erişilemeyen internal service'lere authenticated request gönderebilmesi anlamına gelebilir.
Değerlendirilmesi gereken alanlar:
- Pod CIDR
- Service CIDR
- Node IP range
- Kubelet endpoint'leri
- Kubernetes API server
- Cloud metadata
- Sidecar admin port'ları
- Internal DNS suffix'leri
- Service mesh egress behavior
- Namespace'ler arası NetworkPolicy
- Host network kullanan workload'lar
Default-deny egress iyi bir başlangıçtır. Fakat DNS'e izin vermek için yazılan geniş UDP ve TCP rule'ları, egress gateway bypass'ı veya CNI limitation'ı ayrıca test edilmelidir.
Internal service'e erişmesi gereken workload ile public internet fetcher aynı pod'da veya aynı identity altında çalışmamalıdır.
Güvenli URL fetch akışı için adım adım algorithm
Aşağıdaki sıra yalnızca implementation detayı değildir. Adımların yeri değiştiğinde validation ile connection arasında boşluk oluşabilir.
1. Business contract'i tanımlayın
Önce request'in ne amaçla yapıldığını belirleyin:
- Görsel import
- Webhook
- PDF download
- Link preview
- Internal API call
Her use case için method, allowed scheme, destination modeli, content type, size, timeout ve authentication farklı olabilir. Tek bir “her şeyi fetch eden” generic helper bütün uygulamaya açılmamalıdır.
2. URL yerine ID kullanılıp kullanılamayacağını kontrol edin
Mümkünse burada URL probleminden çıkın. Server-side mapping kullanın.
3. URL'yi tek bir standart parser ile parse edin
Raw string üzerinde security decision vermeyin. Parse başarısızsa reddedin. Parser'ın absolute URL, userinfo, port ve IPv6 davranışını test edin.
4. Scheme'i sınırlandırın
Çoğu use case için yalnızca https yeterlidir. HTTP gerçekten gerekiyorsa destination bazında açıkça tanımlanmalıdır.
5. Userinfo ve fragment'i reddedin
Userinfo phishing ve parser confusion riskini artırır. Fragment HTTP request'inde server'a gönderilmez ve fetch use case'inde çoğunlukla gereksizdir.
6. Host'u canonical hale getirin
Parser output'u üzerinden lower-case, trailing dot ve IDN policy'sini uygulayın. Invalid veya belirsiz değerleri düzeltmek yerine reddedin.
7. Exact destination policy'yi kontrol edin
Host, scheme ve port tuple'ı policy'de bulunmalıdır. Wildcard yalnızca zorunluysa ve ownership güvence altındaysa kullanılmalıdır.
8. DNS answer'larının tamamını alın
Hem IPv4 hem IPv6 değerlendirilmelidir. Empty answer, parse edilemeyen answer veya policy dışı tek address fail closed sonucu üretmelidir.
9. Address policy'yi uygulayın
Sabit destination modelinde expected IP veya CIDR kullanılabilir. Public arbitrary fetch modelinde internal ve special-purpose range'ler engellenir. Organization-specific network inventory eklenir.
10. Doğrulanan IP'ye connection açın
Hostname'i yeniden çözecek generic client path'ine dönmeyin. Connection IP'ye giderken original hostname TLS SNI ve Host header için korunur.
11. Redirect'i kapatın
Gerekiyorsa her hop baştan doğrulanır.
12. Request'i server-controlled değerlerle oluşturun
Method, header ve body template'i application belirler. Incoming header seti forward edilmez.
13. Resource limitlerini uygulayın
Deadline, connection timeout, response size, header size, content type ve concurrency limitleri devrede olmalıdır.
14. Decision ile connection'ı loglayın
Canonical host ve resolved IP birlikte kaydedilmelidir. Böylece “policy hangi değere izin verdi ve socket nereye bağlandı?” sorusu cevaplanabilir.
Test edilmiş Node.js güvenli fetcher örneği
Aşağıdaki örnek belirli ve önceden bilinen destination'lar içindir. Her internet URL'sini güvenli biçimde fetch eden evrensel bir library değildir.
Örneğin güvenlik özellikleri:
- Node.js built-in modülleri dışında package kullanmaz
- WHATWG
URLparser kullanır - Yalnızca HTTP ve HTTPS kabul eder
- Userinfo ve fragment'i reddeder
- Exact hostname policy uygular
- Scheme ve port'u destination bazında kontrol eder
- DNS answer'larının tamamını expected IP setiyle karşılaştırır
- Bir answer bile beklenmeyense request'i reddeder
- Connection'ı doğrulanan IP'ye pin eder
- Original hostname'i TLS
servernameve HTTPHostiçin korur - Redirect'i otomatik takip etmez
- Incoming header forward etmez
- Timeout, header ve body size limiti uygular
- Automatic connection reuse kullanmaz
import dns from "node:dns/promises"
import http from "node:http"
import https from "node:https"
import net from "node:net"
function normalizeHost(hostname) {
let host = hostname.toLowerCase()
if (host.endsWith(".")) {
host = host.slice(0, -1)
}
if (!host || host.endsWith(".")) {
throw new Error("Invalid hostname")
}
if (!/^[a-z0-9.-]+$/.test(host)) {
throw new Error("Hostname must be normalized ASCII")
}
if (net.isIP(host) !== 0) {
throw new Error("IP literals are not accepted")
}
if (
host.length > 253 ||
host.split(".").some(label => {
return (
label.length === 0 ||
label.length > 63 ||
label.startsWith("-") ||
label.endsWith("-")
)
})
) {
throw new Error("Invalid hostname label")
}
return host
}
function expectedAddressList(addresses) {
const list = new net.BlockList()
for (const address of addresses) {
const family = net.isIP(address)
if (family === 0) {
throw new Error(`Invalid policy IP address: ${address}`)
}
list.addAddress(address, family === 4 ? "ipv4" : "ipv6")
}
return list
}
export async function validateAndResolve(
rawURL,
policies,
resolver = dns.lookup,
) {
let target
try {
target = new URL(rawURL)
} catch (error) {
throw new Error("Invalid URL", { cause: error })
}
if (!["http:", "https:"].includes(target.protocol)) {
throw new Error("Only http and https are allowed")
}
if (target.username || target.password) {
throw new Error("URL userinfo is not allowed")
}
if (target.hash) {
throw new Error("URL fragments are not allowed")
}
const host = normalizeHost(target.hostname)
const policy = policies.get(host)
if (!policy) {
throw new Error("Host is not allowlisted")
}
if (!policy.protocols.includes(target.protocol)) {
throw new Error("Protocol is not allowed for this host")
}
const port = target.port || (target.protocol === "https:" ? "443" : "80")
if (!policy.ports.includes(port)) {
throw new Error("Port is not allowed for this host")
}
const answers = await resolver(host, {
all: true,
verbatim: true,
})
if (!Array.isArray(answers) || answers.length === 0) {
throw new Error("Host did not resolve")
}
const allowedAddresses = expectedAddressList(policy.addresses)
for (const answer of answers) {
const family = answer.family === 4 ? "ipv4" : "ipv6"
if (!allowedAddresses.check(answer.address, family)) {
throw new Error(
`DNS answer is outside the expected set: ${answer.address}`,
)
}
}
return {
target,
host,
port,
pinnedAddress: answers[0].address,
}
}
export async function safeFetch(rawURL, policies, options = {}) {
const {
resolver = dns.lookup,
timeoutMs = 5000,
maxBodyBytes = 1024 * 1024,
} = options
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
throw new Error("timeoutMs must be a positive safe integer")
}
if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes <= 0) {
throw new Error("maxBodyBytes must be a positive safe integer")
}
const validated = await validateAndResolve(rawURL, policies, resolver)
const requestModule =
validated.target.protocol === "https:" ? https : http
const hostHeader =
validated.target.port === ""
? validated.host
: `${validated.host}:${validated.port}`
return await new Promise((resolve, reject) => {
let settled = false
const finish = (error, value) => {
if (settled) {
return
}
settled = true
if (error) {
reject(error)
} else {
resolve(value)
}
}
const request = requestModule.request(
{
protocol: validated.target.protocol,
hostname: validated.pinnedAddress,
port: validated.port,
method: "GET",
path: `${validated.target.pathname}${validated.target.search}`,
servername: validated.host,
rejectUnauthorized: true,
agent: false,
maxHeaderSize: 32 * 1024,
signal: AbortSignal.timeout(timeoutMs),
headers: {
Host: hostHeader,
"User-Agent": "Secnodex-SafeFetcher/1.0",
},
},
response => {
if (
response.statusCode === undefined ||
response.statusCode < 200 ||
response.statusCode > 299
) {
response.destroy()
finish(
new Error(`Unexpected HTTP status: ${response.statusCode}`),
)
return
}
const chunks = []
let received = 0
response.on("data", chunk => {
received += chunk.length
if (received > maxBodyBytes) {
response.destroy()
finish(
new Error("Response body exceeds the configured limit"),
)
return
}
chunks.push(chunk)
})
response.on("end", () => {
finish(null, Buffer.concat(chunks))
})
response.on("error", error => {
finish(error)
})
},
)
request.setTimeout(timeoutMs, () => {
request.destroy(new Error("Request timed out"))
})
request.on("error", error => {
finish(error)
})
request.end()
})
}Örnek policy:
const policies = new Map([
[
"fetch.corp.example",
{
protocols: ["https:"],
ports: ["443"],
addresses: ["203.0.113.10", "2001:db8::10"],
},
],
])
const body = await safeFetch(
"https://fetch.corp.example/report.pdf",
policies,
{
timeoutMs: 5000,
maxBodyBytes: 2 * 1024 * 1024,
},
)Örnekteki 203.0.113.0/24 ve 2001:db8::/32 range'leri documentation için ayrılmıştır. Production policy'ye gerçek destination address'leri yazılmalıdır.
Kod neden hostname'i doğruladıktan sonra IP'ye bağlanıyor?
validateAndResolve() DNS answer setini kontrol ediyor. Sonra safeFetch() request socket'ini pinnedAddress değerine açıyor.
Eğer ikinci fonksiyon original hostname'e bağlansaydı Node.js yeni DNS query yapabilirdi. Bu durumda validation sırasında görülen address ile connection sırasında kullanılan address farklı olabilirdi.
HTTPS request'te servername: validated.host ayarı certificate validation'ın original hostname'e göre yapılmasını sağlar. Host header da original host'u taşır. Böylece socket IP'ye giderken TLS ve HTTP virtual host semantics korunur.
Certificate validation kapatılmaz. rejectUnauthorized: true açık kalır.
Kod neden bütün DNS answer'larını reddedebiliyor?
Policy'de iki address varsa resolver yalnızca bu set içinden cevap döndürebilir. Resolver output'una üçüncü ve beklenmeyen bir address eklenirse request fail closed olur.
Bu karar yüksek güvenlikli, sabit destination akışı için uygundur. Büyük CDN arkasındaki external service'te address listesi sık değişiyorsa exact IP policy operational burden yaratır. Böyle bir durumda:
- Provider'ın yayınladığı CIDR seti
- Dedicated egress proxy
- PrivateLink veya benzeri private connectivity
- DNS firewall
- Network firewall
- Provider-specific authenticated endpoint
gibi seçenekler değerlendirilmelidir.
Bu kod production'a aynen alınmalı mı?
Hayır. Örnek güvenlik pattern'ini görünür hale getirir. Production implementation şu context'lere göre uyarlanmalıdır:
- Node.js version
- Proxy ve service mesh kullanımı
- IPv6 policy
- DNS resolver topology
- Connection pooling ihtiyacı
- Content-Type ve file format
- Retry behavior
- TLS policy
- Observability standardı
- Tenant isolation
- Provider IP lifecycle
Örneğin connection pooling yeniden kullanılacaksa pool key'in original destination ve validated IP ile doğru ilişkisi incelenmelidir. DNS seti değiştiğinde stale connection davranışı belirlenmelidir.
Kod için uygulanan unit test'ler
Örnek Node.js v24.14.0 üzerinde node:test ve node:assert modülleriyle çalıştırıldı. Dokuz testin tamamı geçti. Test seti şu davranışları doğruladı:
- 1Exact allowlisted hostname ve beklenen IPv4 ile IPv6 answer kabul edilir
- 2
fetch.corp.example.attacker.invalidsuffix confusion reddedilir - 3URL userinfo reddedilir
- 4DNS setinde bir izinli, bir private address varsa tamamı reddedilir
- 5Allowlist dışı port reddedilir
- 6HTTP ve HTTPS dışındaki scheme reddedilir
- 7Redirect response otomatik takip edilmez
- 8Body size üst sınırı aşılırsa response kesilir
- 9İzin verilen response başarıyla döndürülür
Testte redirect endpoint'i /secret path'ine 302 döndürür. Fetcher 302 response'u error olarak ele alır ve /secret endpoint'ine ikinci request göndermez.
Security test corpus yalnızca birkaç bilinen payload'dan oluşmamalıdır. Parser ve address edge case'leri runtime update'lerinde regression test olarak çalıştırılmalıdır.
Public URL fetch etmek zorunluysa ne yapılmalı?
Link preview, avatar import veya webhook gibi use case'lerde exact hostname allowlist mümkün olmayabilir. Bu durumda “allowlist yoksa çözüm yok” denmemelidir. Architecture farklı kurulmalıdır.
Public-only fetcher modeli
Dedicated fetcher yalnızca public internet destination'larına erişebilir. Internal network'e route'u yoktur. Application destination'ı validate etse bile nihai engelleme egress proxy veya firewall tarafından yapılır.
Önerilen akış:
User
↓
Main application
↓ authenticated internal job
Public fetcher
↓ egress proxy
Public internetFetcher'dan şu yollar kapalıdır:
Cloud metadata
Cluster service network
Database network
Admin network
Corporate VPN routes
Main application private subnetPublic address classification
Public fetcher A ve AAAA result'larının tamamını inceler. Organization internal CIDR'ları ve IANA special-purpose range'leri reddeder. DNS cevabı onaylandıktan sonra socket doğrulanan IP'ye açılır.
Yalnızca RFC 1918 filter yeterli değildir. IPv6 ve provider-specific metadata endpoint'leri dahil edilmelidir.
Redirect policy
Link preview için redirect gerekebilir. Her hop:
- Yeniden parse edilir
- Yeniden DNS resolve edilir
- Yeniden address policy'den geçer
- Doğrulanan IP'ye pin edilir
- Sensitive header olmadan çağrılır
Maksimum üç hop gibi düşük bir limit uygulanabilir. Origin downgrade, örneğin HTTPS'ten HTTP'ye geçiş, use case gerektirmiyorsa reddedilir.
Content policy
Avatar import yalnızca görsel bekliyorsa:
- Allowed MIME type seti tanımlanır
- Magic byte doğrulaması yapılır
- Maksimum pixel count uygulanır
- File parser sandbox içinde çalışır
- SVG gibi active content formatları ayrıca değerlendirilir
- Response object storage'a yazılmadan malware scanning uygulanabilir
Content-Type: image/jpeg header'ı tek başına file format kanıtı değildir.
Webhook için challenge verification
Webhook registration sırasında destination'a random challenge gönderilebilir. Endpoint challenge'ı doğru cevapladığında ownership veya control hakkında ek signal oluşur. Fakat challenge verification SSRF filter'ın yerine geçmez. Internal service de challenge'ı yansıtabiliyorsa risk sürebilir.
Webhook execution worker'ı da public-only network'te çalışmalıdır. Retry sırasında URL yeniden validate edilmelidir. Registration anındaki güvenli address'in günler sonra aynı kalacağı varsayılmamalıdır.
Webhook, image import ve link preview aynı policy'yi kullanmalı mı?
Hayır. Üçü de URL alıyor gibi görünse de capability ve risk farklıdır.
| Use case | Method | Redirect | Expected content | Authentication | Özel risk |
|---|---|---|---|---|---|
| Webhook | Çoğunlukla POST | Genellikle kapalı | Küçük response | HMAC signature | Retry ve side effect |
| Image import | GET | Sınırlı olabilir | Image | Yok | Parser ve decompression |
| Link preview | GET | Gerekebilir | HTML | Yok | Geniş public access |
| PDF import | GET | Sınırlı | Destination-specific olabilir | File parser | |
| Internal API | Sabit method | Kapalı | JSON | Scoped service identity | Lateral movement |
Tek helper'ın bütün use case'ler için method, header ve redirect parameter'larını caller'a açması capability'yi gereksiz genişletir.
Her use case için dar interface daha güvenlidir:
fetchApprovedDocument(documentID)
sendVerifiedWebhook(endpointID, event)
fetchPublicImage(url, maxBytes)
createLinkPreview(url)Bu function'ların arkasında ortak lower-level library bulunabilir, fakat policy caller'ın serbest parametreleriyle gevşetilmemelidir.
SSRF remediation sırasında en sık yapılan hatalar
Yalnızca localhost string'ini block etmek
Loopback IP, IPv6 loopback, alternative representation ve DNS name kullanılabilir. Üstelik hedef yalnızca localhost değildir.
Yalnızca RFC 1918 range'lerini block etmek
Link-local, metadata, IPv6, cluster CIDR ve organization-specific network'ler açık kalır.
Raw URL üzerinde regex kullanmak
Regex parser değildir. Authority, userinfo, port ve encoding edge case'leri yanlış yorumlanabilir.
URL'yi iki farklı library ile parse etmek
Validation ile request farklı result üretebilir. Bir parser host'u başka, client başka yorumlarsa güvenlik kararı bozulur.
DNS'i kontrol edip hostname'e bağlanmak
İkinci resolution sonucu değişebilir. Doğrulanan IP'ye pinning gerekir.
Yalnızca ilk DNS answer'ını kontrol etmek
Retry veya address selection policy dışı answer'ı kullanabilir.
Redirect'i unutmak
İlk destination izinli, ikinci destination yasak olabilir.
Wildcard domain'i kolay çözüm sanmak
Alt domain'lerin tamamı aynı ownership ve security posture'a sahip olmayabilir.
HTTP client'ın proxy davranışını incelememek
Gerçek DNS resolution ve connection proxy'de gerçekleşebilir.
Header'ları topluca forward etmek
SSRF credential exposure ve authenticated lateral movement üretir.
Cloud metadata hardening'i tek çözüm saymak
Metadata dışındaki internal target'lar devam eder.
Fix'i yalnızca vulnerable endpoint'te yapmak
Aynı unsafe helper background job, webhook worker, file importer veya admin feature içinde kullanılabilir.
Mevcut uygulamada SSRF bulunduğunda nasıl müdahale edilmeli?
Remediation yalnızca code patch ile sınırlı kalmamalıdır.
1. Capability'yi belirleyin
Attacker şunların hangisini kontrol edebiliyordu?
- Full URL
- Scheme
- Host
- Port
- Path
- Query
- Method
- Header
- Body
- Redirect target
2. Reachability'yi haritalayın
Vulnerable runtime'dan erişilebilen alanları belirleyin:
- Metadata
- Internal API
- Database proxy
- Management interface
- Kubernetes service
- Node service
- Monitoring endpoint
- Message broker
- Serverless control endpoint
3. Identity exposure'u değerlendirin
Process hangi token, certificate, cloud role veya cookie'ye sahipti? Downstream request'e otomatik header ekleniyor muydu?
4. Log evidence'i koruyun
Application log, DNS log, egress proxy, firewall, cloud audit ve downstream access log'ları korelasyon için saklanmalıdır.
5. Geçici network containment uygulayın
Code deployment beklenirken vulnerable workload'un metadata ve internal route erişimi egress policy ile kapatılabilir. Feature geçici olarak devre dışı bırakılabilir.
6. Credential rotation kararı verin
Metadata, internal token endpoint veya authenticated service erişimi mümkünse ilgili credential'lar rotate edilmelidir. “Response kullanıcıya dönmüyordu” tek başına exposure olmadığını kanıtlamaz.
7. Root cause'u ortak component'te düzeltin
URL fetch eden bütün code path'ler bulunmalıdır. Background queue ve retry worker'ları unutulmamalıdır.
8. Regression test ekleyin
Bulunan bypass, unit ve integration test corpus'una eklenmelidir.
9. Network architecture'ı kalıcı olarak düzeltin
Application bug'ın tekrar edebileceği kabul edilerek egress blast radius azaltılmalıdır.
SSRF finding'inin scope ve retest yaklaşımı, genel web uygulaması sızma testi kapsamı içinde yalnızca tek endpoint ile sınırlandırılmamalıdır. Aynı outbound request helper'ını kullanan API, worker ve scheduled job'lar birlikte ele alınmalıdır.
SSRF Secure Code Review sırasında nasıl aranmalı?
SAST source'tan network sink'e data flow bulabilir. Fakat gerçek risk çoğu zaman wrapper behavior, framework default'u, network topology ve business requirement ile anlaşılır.
Secure Code Review sırasında şu source'lar aranır:
- URL parameter
- Webhook configuration
- File import URL'si
- Avatar veya media URL'si
- PDF generation asset URL'si
- XML external entity
- Template içindeki remote include
- Proxy endpoint
- Callback URL
- Redirect destination
- Database'de saklanan ve sonra worker tarafından çağrılan URL
Sink örnekleri:
- HTTP client
- URL opener
- Browser automation
- Headless browser navigation
- Image processing library remote loader
- PDF renderer
- Cloud SDK download helper
- Socket API
- FTP veya SMB client
- XML parser
Review yalnızca source ile sink arasında validateURL() function'ı var mı diye bakmamalıdır. Şunlar doğrulanmalıdır:
- Validation hangi representation üzerinde?
- Client redirect takip ediyor mu?
- DNS validation sonucu connection'da kullanılıyor mu?
- Proxy behavior ne?
- Retry farklı code path kullanıyor mu?
- Async worker URL'yi tekrar validate ediyor mu?
- Header ve credential nasıl ekleniyor?
- Egress route ne?
- IPv6 ele alınıyor mu?
- Fail open behavior var mı?
Kaynak kod analizi ile runtime testin neden aynı problemi çözmediğini kaynak kod analizi ile sızma testi nerede ayrışır? yazımızda ayrıntılı olarak ele alıyoruz.
Automated SAST kuralı unsafe HTTP client çağrılarını ve user-controlled URL flow'larını bulabilir. Fakat custom wrapper'ın DNS pinning yapıp yapmadığını veya Kubernetes egress policy'nin gerçekten çalıştığını tek başına kanıtlayamaz. Secure Code Review ile otomatik SAST taraması farkı bu ayrımı daha geniş context'te açıklar.
SSRF sızma testinde nasıl doğrulanmalı?
Yetkili testte amaç yalnızca metadata response almak değildir. Kontrolün hangi sınırda kırıldığını ve gerçek blast radius'i güvenli biçimde ölçmektir.
Test planı şu başlıkları içermelidir:
Input surface
- Full URL
- Host-only field
- Webhook
- Import feature
- PDF veya screenshot
- XML parser
- Server-side redirect
- GraphQL resolver
- Mobile backend
- Admin panel
- Background worker
Parser behavior
- Userinfo
- Trailing dot
- Case variation
- Encoded delimiter
- Unicode ve Punycode
- IPv6 literal
- IPv4-mapped IPv6
- Explicit default port
- Invalid ama farklı parser tarafından kabul edilebilen URL
DNS behavior
- A ve AAAA
- Multiple answer
- Mixed public ve internal result
- CNAME chain
- DNS result change
- Resolver failure
- Split-horizon response
Redirect behavior
- Relative redirect
- Absolute redirect
- Cross-origin redirect
- Scheme downgrade
- Redirect loop
- Redirect chain limit
- Sensitive header forwarding
Network reachability
- Loopback
- Link-local
- Internal CIDR
- Metadata
- Cluster network
- Management network
- Organization-specific service range
Response handling
- Large body
- Large header
- Slow response
- Compressed response
- Unexpected MIME type
- Connection stall
- Partial body
Testler production stability'sini bozmayacak güvenli canary endpoint'lerle yapılmalıdır. Internal port range'e yüksek hızda scan göndermek veya state-changing endpoint çağırmak ayrı authorization gerektirir.
SSRF acceptance checklist
Product design
- [ ] Kullanıcıdan full URL almak gerçekten zorunlu
- [ ] URL yerine resource ID veya predefined destination key değerlendirildi
- [ ] Use case'e özel dar fetch interface'i var
- [ ] Arbitrary public fetch ile internal API call ayrıldı
URL parsing
- [ ] Tek ve standart URL parser kullanılıyor
- [ ] Absolute URL zorunlu
- [ ] Userinfo reddediliyor
- [ ] Scheme allowlist uygulanıyor
- [ ] Host parser output'undan alınıyor
- [ ] Port destination bazında sınırlandırılıyor
- [ ] IDN ve trailing dot policy'si tanımlı
- [ ] IPv4 ve IPv6 test edildi
DNS ve connection
- [ ] A ve AAAA answer'larının tamamı kontrol ediliyor
- [ ] Organization internal range'leri merkezi inventory'den geliyor
- [ ] IANA special-purpose address'ler ele alınıyor
- [ ] Mixed DNS answer fail closed sonuç üretiyor
- [ ] Doğrulanan IP connection'da gerçekten kullanılıyor
- [ ] Original hostname TLS SNI için korunuyor
- [ ] Certificate validation kapatılmıyor
Redirect
- [ ] Default redirect behavior doğrulandı
- [ ] Redirect gerekmiyorsa kapalı
- [ ] Gerekiyorsa her hop yeniden validate ediliyor
- [ ] Hop limiti ve loop detection var
- [ ] Sensitive header cross-origin taşınmıyor
Request
- [ ] Method server-controlled
- [ ] Incoming header'lar topluca forward edilmiyor
- [ ] Destination-specific credential kullanılıyor
- [ ] Request body size sınırlı
- [ ] Proxy behavior açıkça tanımlı
Response
- [ ] Toplam deadline var
- [ ] Connection ve response timeout var
- [ ] Header size limit var
- [ ] Body size streaming sırasında ölçülüyor
- [ ] Automatic decompression riski değerlendirildi
- [ ] Content-Type ve file signature kontrolü var
- [ ] Downstream parser sandbox içinde
Network
- [ ] Default-deny egress değerlendirildi
- [ ] Metadata IPv4 ve IPv6 endpoint'leri engelli
- [ ] Internal subnet ve control plane route'ları kapalı
- [ ] Public fetcher ayrı network segment'inde
- [ ] Egress policy'nin gerçekten enforce edildiği test edildi
Operation
- [ ] Allowlist item'larının owner ve review tarihi var
- [ ] DNS değişiklikleri izleniyor
- [ ] Rejection reason loglanıyor
- [ ] Canonical host ile connection IP birlikte görülebiliyor
- [ ] Retry ve async worker fetch anında yeniden validate ediyor
- [ ] Regression test corpus'u CI/CD'de çalışıyor
Yönetim açısından “SSRF kapatıldı” ne zaman denebilir?
Bir finding'in code status'u Resolved olduğunda değil, aşağıdaki evidence birlikte bulunduğunda:
- 1Attacker-controlled network component'leri kaldırılmış veya daraltılmıştır
- 2Parser ve canonicalization behavior test edilmiştir
- 3Scheme, host ve port policy'si destination'ı doğru tanımlamaktadır
- 4DNS answer ile connection destination arasında binding vardır
- 5Redirect behavior güvenlidir
- 6Header ve credential forwarding sınırlandırılmıştır
- 7Egress network internal target'ları bağımsız olarak engeller
- 8Timeout ve response limitleri uygulanır
- 9Async ve retry code path'leri aynı policy'yi kullanır
- 10Regression ve integration testleri geçer
- 11Production-like ortamda retest yapılır
- 12Allowlist lifecycle sahibi belirlenir
Bu evidence yoksa çözüm çoğunlukla “belirli payload artık çalışmıyor” seviyesinde kalır.
Sonuç: Allowlist bir karar noktasıdır, tek başına güvenlik sınırı değildir
Allowlist SSRF savunmasının en değerli application layer kontrollerinden biridir. Özellikle destination'ların az, sabit ve güvenilir olduğu sistemlerde arbitrary network access'i küçük bir setle sınırlar.
Fakat allowlist'in verdiği güvence yalnızca şu soruya yakındır:
Kısa cevap
Parse ettiğim representation içindeki host, izin verdiğim değerlerden biri mi?
SSRF'nin gerçek sorusu daha büyüktür:
Kısa cevap
Uygulama hangi identity, protocol ve network route ile gerçekte nereye bağlandı ve response'u nasıl işledi?
Bu iki soru arasında URL parser, canonicalization, DNS, IPv4, IPv6, redirect, proxy, TLS, header forwarding ve egress topology bulunur.
Sağlam çözüm şu parçaları birlikte kurar:
- 1Mümkünse full URL yerine server-side resource mapping
- 2Exact scheme, host ve port policy
- 3Tek parser ve canonical representation
- 4Bütün DNS answer'larının doğrulanması
- 5Doğrulanan IP'ye connection pinning
- 6Original hostname ile TLS doğrulaması
- 7Redirect'in kapatılması veya her hop'un yeniden kontrolü
- 8Server-controlled method ve header
- 9Timeout, header, body ve concurrency limitleri
- 10Dedicated düşük yetkili fetcher
- 11Network seviyesinde egress restriction
- 12DNS ve connection telemetry
- 13Regression test ve production-like retest
Dolayısıyla doğru cevap nettir:
Kısa cevap
SSRF'yi kapatmak için yalnızca allowlist yeterli değildir. Allowlist, validation sonucunu gerçek connection destination'a ve bağımsız network egress policy'sine bağladığında güvenilir hale gelir.
Secnodex, SSRF değerlendirmesini yalnızca birkaç URL payload'ı denemek olarak ele almaz. Application'ın outbound request flow'larını, parser ve HTTP client davranışını, DNS resolution ile connection arasındaki ilişkiyi, redirect ve proxy configuration'ını, cloud metadata exposure'unu, Kubernetes egress yapısını ve source code içindeki ortak fetcher helper'larını birlikte inceler. Mevcut SSRF finding'inizin kalıcı biçimde kapanıp kapanmadığını doğrulamak, Web Uygulama Güvenliği testi yaptırmak veya Secure Code Review kapsamında outbound request architecture'ınızı değerlendirmek için Secnodex ile iletişime geçebilirsiniz.
Kaynaklar
- OWASP Cheat Sheet Series – Server-Side Request Forgery Prevention
- MITRE CWE-918 – Server-Side Request Forgery
- RFC 3986 – Uniform Resource Identifier Generic Syntax
- IANA – IPv4 Special-Purpose Address Space
- IANA – IPv6 Special-Purpose Address Space
- AWS EC2 – Configure the Instance Metadata Service Options
- AWS EC2 – Access Instance Metadata
- Google Cloud – View and Query VM Metadata
- Microsoft Azure – Instance Metadata Service
- Kubernetes Documentation – Network Policies
- Node.js Documentation – URL
- Node.js Documentation – DNS
- Node.js Documentation – HTTP
- Node.js Documentation – HTTPS
- Node.js Documentation – Net
Sık sorulan sorular
SSRF'yi kapatmak için yalnızca domain allowlist yeterli mi?
Hayır. Domain allowlist güçlü bir başlangıçtır, fakat URL parsing, DNS answer, connection IP, scheme, port, redirect, proxy, header ve egress policy birlikte ele alınmalıdır.
Allowlist yerine blocklist kullanılabilir mi?
Hedefler önceden biliniyorsa allowlist daha güçlüdür. Public web'e erişim gerekiyorsa exact domain allowlist uygulanamaz. Bu durumda internal ve special-purpose destination'ları block eden application ve network controls birlikte kullanılır.
Private IP'leri block etmek SSRF'yi çözer mi?
Hayır. IPv6, link-local, metadata, loopback, special-purpose range, organization-specific internal network ve redirect gibi alanlar devam eder. Public destination üzerinden data exfiltration veya request abuse da mümkün olabilir.
DNS rebinding nedir?
Aynı hostname'in validation ve connection zamanında farklı IP sonuçları üretmesidir. Uygulama public address'i onaylayıp daha sonra hostname'i yeniden çözdüğünde internal address'e bağlanabilir.
DNS rebinding'e karşı ne yapılmalı?
Bütün A ve AAAA answer'ları doğrulanmalı, policy dışı tek answer request'i durdurmalı ve connection doğrulanan IP'ye açılmalıdır. Egress firewall da internal destination'ı engellemelidir.
Redirect tamamen kapatılmalı mı?
Gerekmiyorsa evet. Gerekiyorsa her redirect hop'u yeni URL gibi parse, resolve ve validate edilmelidir. Hop limiti ve sensitive header stripping uygulanmalıdır.
HTTP client'ta redirect kapalı olduğunu nasıl anlarız?
Library documentation ve integration test birlikte kullanılmalıdır. Bir test server 302 döndürüp ikinci endpoint'e hit gelip gelmediğini kaydedebilir. Default davranış runtime veya wrapper'a göre değişebilir.
`startsWith` ile URL allowlist yapılabilir mi?
Hayır. Raw string prefix kontrolü authority, userinfo, port ve encoding farklarını güvenilir biçimde ele almaz. URL standart parser ile ayrıştırılmalı ve host exact comparison ile doğrulanmalıdır.
Wildcard subdomain allowlist güvenli mi?
Exact host allowlist'ten daha geniş risk taşır. Alt domain ownership, dangling DNS, third-party delegation ve subdomain takeover ihtimali yönetilemiyorsa tercih edilmemelidir.
URL'deki userinfo neden reddedilmeli?
https://trusted.example@attacker.invalid/ örneğinde gerçek host attacker.invalid olur. Userinfo hem insan yorumunu hem zayıf string kontrollerini yanıltabilir.
IPv6 kontrolü gerçekten gerekli mi?
Evet. Runtime ve network IPv6 destekliyorsa yalnızca IPv4 filter uygulanması açık bırakır. IPv6 loopback, unique-local, link-local, mapped address ve provider endpoint'leri değerlendirilmelidir.
Cloud'da IMDSv2 kullanmak SSRF'yi çözer mi?
IMDSv2 önemli defense in depth sağlar, fakat arbitrary outbound request capability'sini ve internal service erişimini ortadan kaldırmaz. Application ve egress kontrolleri yine gereklidir.
`Metadata-Flavor` veya `Metadata: true` header requirement yeterli mi?
Custom header gönderemeyen basit SSRF primitive'lerine karşı riski azaltır. Ancak client header kontrolüne izin veriyorsa veya başka internal target'lar varsa kök sorun sürer.
Webhook URL'sine allowlist nasıl uygulanır?
Müşteri domain'leri önceden bilinmediği için exact allowlist çoğu SaaS modelinde mümkün değildir. Public-only webhook worker, per-request DNS ve IP validation, redirect kapatma, egress isolation, challenge verification ve HMAC signature birlikte kullanılabilir.
Allowlist'teki domain CDN kullanıyorsa ne yapılmalı?
Provider'ın yayınladığı IP range, dedicated egress proxy, private connectivity veya provider-specific endpoint seçenekleri incelenmelidir. Bütün internet IP'lerine izin verip yalnızca hostname string'ine güvenmek yeterli değildir.
TLS certificate doğrulaması neden önemlidir?
IP pinning yapılırken original hostname üzerinden certificate validation korunmazsa attacker veya yanlış route farklı server'a bağlanabilir. Certificate validation hiçbir zaman SSRF fix'i uğruna kapatılmamalıdır.
Host header original domain mi olmalı?
Evet. Connection validated IP'ye açılırken virtual host seçimi için HTTP Host header original domain'i taşımalıdır. Header kullanıcıdan alınmamalı, validated host üzerinden server tarafından üretilmelidir.
Proxy kullanıyorsak IP pinning nerede yapılmalı?
Proxy connection'ı kendisi açıyorsa final DNS ve destination enforcement proxy tarafında yapılmalıdır. Application validation ile proxy policy tutarlı olmalı, proxy internal route'ları block etmelidir.
WAF SSRF'yi engeller mi?
WAF bazı bilinen payload ve IP pattern'lerini yakalayabilir. Fakat DNS değişimi, parser farkı, redirect, IPv6 ve business-specific outbound request akışını güvenilir biçimde çözemez. WAF tamamlayıcı kontroldür.
SAST SSRF'yi bulabilir mi?
SAST user-controlled data'nın HTTP client veya URL opener sink'ine akışını bulabilir. Custom sanitizer, DNS pinning, redirect behavior ve network egress doğrulaması için Secure Code Review ve runtime test gerekir.
Sızma testi SSRF fix'ini nasıl doğrular?
Parser edge case, DNS answer değişimi, IPv4 ve IPv6, redirect, port, scheme, header, timeout ve network reachability test edilir. Retest yalnızca ilk payload'ın artık block edildiğini doğrulamakla bitmemelidir.
Allowlist ne sıklıkta gözden geçirilmeli?
Asset criticality ve vendor lifecycle'a göre düzenli review yapılmalıdır. DNS ownership, IP range, certificate, path scope, business owner ve kullanım durumu değiştiğinde event-based review da tetiklenmelidir.
Background worker neden ayrıca kontrol edilmeli?
URL registration anında validate edilse bile DNS daha sonra değişebilir. Worker fetch anında URL'yi yeniden parse, resolve ve validate etmelidir. Queue içindeki “önceden onaylandı” flag'i kalıcı güven kanıtı değildir.
SSRF finding'i kapandıktan sonra hangi test CI/CD'ye eklenmeli?
Exact host, suffix confusion, userinfo, mixed DNS answer, IPv4 ve IPv6, unapproved port, redirect, timeout, oversized response ve fail-closed error davranışları regression test olarak çalıştırılmalıdır.
Okumaya devam et
Web Uygulama Güvenliği
IDOR Açığı Neden Hâlâ Bu Kadar Yaygın?
IDOR, identifier tahmin edilebildiği için değil, server her object access sırasında doğru authorization kararını veremediği için oluşur. Yaygınlığın mimari ve süreç nedenlerini inceliyoruz.
Yazıyı okuWeb Uygulama Güvenliği
Web Uygulaması Sızma Testinde Kapsam Nasıl Belirlenir?
Web pentest kapsamı yalnızca domain ve ekran sayısı değildir. Uygulama sınırı, API'ler, roller, tenant'lar, iş akışları, test ortamı ve güvenli çalışma kuralları birlikte tanımlanmalıdır.
Yazıyı oku