Merhaba, bir önceki dersimizde bir kullanıcının belirli bir fonksiyon veya endpoint'e erişim yetkisini nasıl kontrol edeceğimizi, modern framework'lerdeki middleware, dekoratör ve annotation gibi desenler üzerinden inceledik. Bu "geçiş kapılarının" analizini tamamladığımıza göre, şimdi bu kapıdan geçen kullanıcının içeride ne yapabildiğine odaklanacağız. Bu, yetkilendirme analizinin ikinci ve daha derin katmanıdır.
Bu dersimizde, OWASP API Security Top 10 listesinin başında yer alan ve pratikte en sık karşılaşılan zafiyetlerden biri olan Kırık Nesne Seviyesinde Yetkilendirme'yi (Broken Object Level Authorization - BOLA), daha yaygın bilinen adıyla Güvensiz Doğrudan Nesne Referansı'nı (Insecure Direct Object Reference - IDOR) kod seviyesinde nasıl tespit edeceğimizi öğreneceğiz. Öğrenim hedefimiz doğrultusunda, bir nesne referansının (örneğin bir sipariş ID'si) kullanıcı tarafından sağlandığı andan veritabanından çekildiği ana kadar olan yolculuğunu izleyecek ve bu süreçteki en kritik adımı, yani "sahiplik kontrolünü" mercek altına alacağız.
Kırık Nesne Seviyesinde Yetkilendirme (BOLA/IDOR) Nedir?
IDOR, bir uygulamanın bir nesneye (örneğin bir veritabanı kaydı, dosya, profil bilgisi) erişmek için kullanıcı tarafından sağlanan bir tanımlayıcıyı (identifier) kullanması, ancak bu nesneye erişmeye çalışan kullanıcının buna yetkisi olup olmadığını doğrulamaması durumunda ortaya çıkan bir zafiyettir.
Basit bir ifadeyle, bir apartmanın ana kapısını açacak anahtarınızın (kimlik doğrulama) olması, ancak bu anahtarla sadece daire numarasını bilerek binadaki tüm dairelere girebilmenizdir. Olması gereken ise, her daire için ayrı bir anahtarınızın (yetkilendirme) olmasıdır.
Aşağıdaki şema, bu durumu özetlemektedir:

Kod denetimindeki temel amacımız, bu sahiplik kontrolünün varlığını ve doğruluğunu teyit etmektir. Sizin gibi hem saldırı (penetrasyon testi) hem de savunma (güvenli mimari tasarımı) tecrübesine sahip bir uzman için bu zafiyetin iki yüzünü de anlamak, onu kodda tespit etmeyi çok daha sezgisel hale getirecektir.
Zafiyetin Kök Nedeni: Eksik Sahiplik Kontrolü
IDOR zafiyetlerinin neredeyse tamamı, veri erişim katmanındaki tek bir eksikliğe dayanır. Geliştirici, kullanıcıdan gelen ID ile nesneyi çeker, ancak o nesnenin o kullanıcıya ait olduğunu doğrulayan ikinci bir koşulu sorguya eklemeyi unutur.
Zafiyetli Mantık:
"Bana id'si 123 olan faturayı getir."SELECT * FROM invoices WHERE id = 123;
Güvenli Mantık:
"Bana id'si 123 olan ve sahibi user_id'si 42 olan kullanıcının faturasını getir."SELECT * FROM invoices WHERE id = 123 AND user_id = 42;
Kod analizi sırasında arayacağımız temel desen tam olarak budur: Kullanıcı kontrollü bir tanımlayıcının kullanıldığı her sorguda, isteği yapan kullanıcının kimliğini içeren (user_id = ?, owner_id = ?, tenant_id = ? gibi) bir AND koşulu var mı?
IDOR Zafiyetlerini Kodda Tespit Etme Yöntemleri
IDOR avı, sistematik bir takip sürecidir:
-
Giriş Noktalarını Belirle: Kullanıcı tarafından kontrol edilebilen tüm tanımlayıcıları (ID'ler) bulun. Bunlar genellikle şu şekillerde karşımıza çıkar:
- URL Yolu (Path):
/api/orders/**12345** - Sorgu Parametreleri (Query String):
/api/users?userId=**abc-123** - İstek Gövdesi (Request Body):
{ "documentId": "**doc-54321**" } - Çerezler ve Gizli Form Alanları: Bazen ID'ler daha az görünür yerlerde saklanabilir.
- URL Yolu (Path):
-
Veriyi Takip Et (Taint Analysis): Bu kullanıcı girdisinin (artık "kirli" veya "tainted" olarak kabul ettiğimiz bu ID'nin) kontrolcü (controller) veya rota işleyici (route handler) fonksiyonundan, veri erişim katmanına (repository, DAO, service) kadar olan yolculuğunu izleyin.
-
Sorguyu Analiz Et: Veri erişim katmanında, bu ID'nin kullanıldığı son noktayı bulun. Bu bir ham SQL sorgusu, bir ORM (Object-Relational Mapping) metot çağrısı veya bir dosya sistemi API çağrısı olabilir.
- ORM (Spring Data JPA, Django ORM vb.):
orderRepository.findById(orderId)gibi bir çağrı genellikle şüphelidir.orderRepository.findByIdAndUserId(orderId, currentUserId)gibi bir çağrı ise genellikle güvenlidir. Metot adları, aradığımız ipucunu sıkça verir. - SQL Sorgu Oluşturucuları (Query Builders):
query.where("id", "=", orderId)ifadesini içeren bir kod bloğunda,query.where("user_id", "=", currentUserId)gibi ikinci bir koşulun eklenip eklenmediğini kontrol edin.
- ORM (Spring Data JPA, Django ORM vb.):
Gerçek Dünya Senaryoları: Pentest Günlükleri
Teoriyi pratiğe dökmek için, gerçek sızma testi bulgularından derlenmiş aşağıdaki kaynağı inceleyelim. Bu örnekler, IDOR'un ne kadar çeşitli ve beklenmedik şekillerde ortaya çıkabileceğini gösteriyor.
Siber Küme tarafından yayınlanan bu belge, sızma testi uzmanlarının karşılaştığı gerçek dünya zafiyetlerini kısa hikayeler şeklinde anlatıyor. IDOR zafiyetlerinin farklı varyasyonlarını ve nasıl istismar edildiklerini görmek için mükemmel bir kaynaktır.
Lütfen aşağıdaki bölümleri okuyun ve her bir senaryodaki temel mantık hatasına odaklanın: Sıradaki Gelsin! bölümünde, tahmin edilebilir (sıralı) nümerik ID'lerin toplu veri sızıntısına nasıl yol açtığına dikkat edin. Bu, en klasik IDOR senaryosudur. Kimlik Kartını Çalmak bölümünde, tahmin edilemeyen bir USER_ID'nin başka bir zafiyetli endpoint üzerinden nasıl sızdırıldığını ve bu iki bulgunun birleştirilerek nasıl hesap ele geçirmeye yol açtığını analiz edin. Bu, "exploit chaining" için harika bir örnektir. Bana Yeni Bir Giriş Bileti Ver bölümünde, parola sıfırlama mekanizmasındaki iki farklı mantık hatasını (hem IDOR hem de geçersiz token kabulü) ve hassas ID'lerin bir rol listeleme API'sinden nasıl elde edildiğini görün. Üyelik Bilgilerine Erişim bölümünde, herhangi bir kimlik doğrulama olmadan, sadece URL'deki ID'yi değiştirerek başka bir üyenin bilgilerine nasıl erişildiğine odaklanın. Bu, kimliği doğrulanmamış (unauthenticated) IDOR'dur. Teknolojiler Değişir, Zafiyet Mantığı Değişmez bölümünde ise referansın URL veya body yerine bir çerezde (cookie) nasıl taşındığını ve en modern mimarilerde bile en temel zafiyetlerin nasıl var olabildiğini gösteren ilginç örneği inceleyin.
Bu örneklerden de görebileceğiniz gibi, IDOR zafiyetinin anahtarı her zaman tahmin edilebilir ID'ler değildir. Asıl sorun, bir kullanıcının, nereden elde ettiği fark etmeksizin, başka bir kullanıcıya ait bir ID ile işlem yapabilmesidir. Bu nedenle, UUID gibi tahmin edilemez ID'ler kullanmak, zafiyeti yalnızca gizler, ortadan kaldırmaz.
Pratik Uygulama: Kodlanmış Referansları Atlatmak
Geliştiriciler bazen ID'leri Base64 gibi yöntemlerle kodlayarak zafiyeti gizlemeye çalışırlar. Bir kod denetçisi veya sızma testi uzmanı olarak bu tür desenleri tanımak ve çözmek önemlidir. Aşağıdaki video, bu sürecin pratik bir uygulamasını göstermektedir.
Canlı Bug Bounty 7 - Broken Access Control
Atıl Samancıoğlu'nun bu videosu, bir bug bounty avcısının IDOR zafiyetini adım adım nasıl bulduğunu gösteriyor. Özellikle Base64 ile kodlanmış bir ID'nin nasıl tespit edilip manipüle edildiğini görmek için çok faydalıdır.
Lütfen aşağıdaki adımları izleyin: İlk olarak, order ID'nin keşfedildiği ve potansiyel bir hedef olarak belirlendiği anı izleyin. Ardından, Burp Suite kullanılarak bu order ID'nin nasıl yakalandığı ve Base64 ile kodlandığının nasıl anlaşıldığı bölümüne odaklanın. Decoder aracının kullanımı burada kritiktir. Zafiyetin doğrulandığı kritik anı izleyin. Base64 ile kodlanmış ID değiştirildiğinde, başka bir kullanıcının bilgilerinin başarıyla alındığını göreceksiniz. Son olarak, zafiyetin kapsamının nasıl test edildiğini, farklı ID'ler (1, 0, 42067 vb.) denenerek yönetici gibi daha ayrıcalıklı hesaplara erişilip erişilemeyeceğinin nasıl araştırıldığını gözlemleyin.
Kod Seviyesinde Çözüm ve Önleme
Şimdi tüm bu bilgileri bir araya getirerek, bir Spring Boot uygulamasında IDOR zafiyetinin nasıl göründüğünü ve nasıl düzeltileceğini inceleyelim.
Zafiyetli Kod (Java/Spring):
@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {
@Autowired
private InvoiceRepository invoiceRepository;
// Bu metot, sadece oturum açmış olmayı kontrol eder.
// Ancak faturanın bu kullanıcıya ait olup olmadığını kontrol etmez.
@GetMapping("/{invoiceId}")
@PreAuthorize("isAuthenticated()")
public Invoice getInvoiceById(@PathVariable Long invoiceId) {
// Zafiyetli kısım: Sadece invoiceId'ye göre arama yapılıyor.
return invoiceRepository.findById(invoiceId)
.orElseThrow(() -> new ResourceNotFoundException("Invoice not found"));
}
}
Bu kod, bir önceki derste öğrendiğimiz @PreAuthorize ile "geçiş kapısı" kontrolünü yapıyor ancak "sahiplik" kontrolünü atlıyor.
Güvenli Kod (Seçenek 1: Manuel Kontrol):
@GetMapping("/{invoiceId}")
@PreAuthorize("isAuthenticated()")
public Invoice getInvoiceById(@PathVariable Long invoiceId, Authentication authentication) {
// Mevcut kullanıcının kimliğini al
UserDetailsImpl currentUser = (UserDetailsImpl) authentication.getPrincipal();
// Güvenli kısım: Hem invoiceId hem de userId'ye göre arama yapılıyor.
return invoiceRepository.findByInvoiceIdAndUserId(invoiceId, currentUser.getId())
.orElseThrow(() -> new ResourceNotFoundException("Invoice not found or not authorized"));
}
Burada, InvoiceRepository içinde findByInvoiceIdAndUserId gibi özel bir metot tanımlandığını varsayıyoruz. Bu, en açık ve anlaşılır çözümdür.
İleri Seviye Güvenli Kod (Seçenek 2: Deklaratif Kontrol):
Spring Security'nin yeteneklerinden tam olarak faydalanarak daha zarif bir çözüm de mümkündür.
@GetMapping("/{invoiceId}")
// @PostAuthorize, metot çalıştıktan SONRA, dönen nesne (returnObject) üzerinde
// bir yetki kontrolü yapar.
@PostAuthorize("returnObject.userId == authentication.principal.id or hasRole('ADMIN')")
public Invoice getInvoiceById(@PathVariable Long invoiceId) {
// Metot içi mantık basitleşti. Güvenlik, deklaratif olarak annotation ile sağlandı.
return invoiceRepository.findById(invoiceId)
.orElseThrow(() -> new ResourceNotFoundException("Invoice not found"));
}
@PostAuthorize kullanımı, iş mantığı kodunu güvenlik kontrollerinden ayırdığı için özellikle temiz ve sürdürülebilir bir çözümdür. Bu, bir admin'in tüm faturaları görebilmesi gibi daha karmaşık kuralları da kolayca ifade etmenizi sağlar.
Sonuç
Bu derste, yetkilendirmenin en kritik yönlerinden biri olan nesne seviyesinde erişim kontrolünü ve bunun eksikliğinden kaynaklanan IDOR zafiyetlerini derinlemesine inceledik.
Temel Çıkarımlar:
- IDOR, bir sahiplik kontrolü hatasıdır. Bir nesneye erişim isteyen kullanıcının, o nesnenin meşru sahibi veya yetkilisi olup olmadığı doğrulanmalıdır.
- Sistematik olun: Kullanıcıdan gelen tüm tanımlayıcıları belirleyin, veri erişim katmanına kadar takip edin ve sorgu mantığını analiz edin.
- Tahmin edilemez ID'ler bir çözüm değildir. UUID'ler veya karmaşık ID'ler sadece zafiyetin keşfedilmesini zorlaştırır, temel mantık hatasını düzeltmez.
- Deklaratif güvenlik kontrollerini kullanın:
@PostAuthorizegibi annotation'lar, nesne seviyesi yetkilendirme kurallarını kodun içinde dağıtmak yerine merkezi ve okunabilir bir şekilde tanımlamak için güçlü araçlardır.
Bir sonraki dersimizde, yetkilendirme kontrollerini atlatmak için kullanılabilecek daha gizli bir tekniğe odaklanacağız: Yol (path) ve URL normalizasyonundaki tutarsızlıkların istismar edilmesi. Bu, uygulamanın bir URL'yi nasıl yorumladığı ve güvenlik filtresinin yorumu ile rota işleyicinin yorumu arasındaki farklardan nasıl faydalanılabileceği üzerine kurulu olacaktır.