İçeriğe geç
Sedat Özdemir
Yazılar

api-security

Görünmez Kapıların Kilidi: IDOR ve API'lerin Arka Kapısındaki Sessiz Misafirler

Bir ID'yi değiştirerek başkasının verisine ulaşmak çok 2000'ler gibi geliyor değil mi? Yanılıyorsun. Modern web dünyasında IDOR (Insecure Direct Object Reference) hala en sinsi ve en yaygın açıkların başında geliyor.

Sedat Özdemir
· 4 dk okuma

Veritabanındaki bir ID'yi sadece 1 artırıp başkasının faturasını veya özel mesajlarını gördüğünde, 'Bunu gerçekten bu kadar kolay yapmış olamazlar' diye o meşhur soğuk terleri döktün mü hiç? Eğer dökmediysen, ya çok şanslı bir sistem yöneticisisin ya da henüz doğru endpoint'e bakmadın demektir.

Selamlar, ben Sedat. Bugün biraz 'eski ama altın' (old but gold) tadında, ancak modern Single Page Application (SPA) ve mikroservis mimarilerinde hala can yakan bir konuyu masaya yatıralım istiyorum: IDOR, nam-ı diğer Insecure Direct Object Reference. Teknik literatürde artık OWASP Top 10 listesinde 'Broken Access Control' başlığı altında toplansa da, biz sahadakiler ona hala o samimi ismiyle hitap etmeyi seviyoruz.

Nedir Bu IDOR'un Derdi?

Mesele aslında çok basit. Yazılımcı arkadaşımız, kullanıcıya ait bir kaynağa (bu bir profil sayfası, fatura, mesaj veya sipariş olabilir) erişim sağlarken, bu kaynağın veritabanındaki benzersiz anahtarını (ID) URL'de veya istek gövdesinde (request body) açıkça kullanıyor. Buraya kadar sorun yok. Sorun, sistemin 'Bu isteği yapan kişi, gerçekten bu ID'li veriyi görmeye yetkili mi?' sorusunu sormayı unuttuğu an başlıyor.

Bir Red Teamer olarak söyleyebilirim ki; karmaşık SQL injection'lar veya 0-day exploit'ler peşinde koşarken, bazen en büyük 'win'i sadece bir rakamı değiştirerek alıyoruz. Bu durum, savunma tarafındaki arkadaşlar için bazen moral bozucu olabiliyor ama gerçek bu.

Klasik Bir Senaryo: testCompany'nin Fatura Portalı

Hayal edelim; testCompany adında bir şirketimiz var. Kullanıcılar giriş yaptıktan sonra faturalarını şu URL üzerinden görüyorlar:

https://api.testcompany.example.com/v1/get-invoice?id=5501

Burada 5501 benim faturamın ID'si olsun. Eğer ben bu 5501'i 5500 veya 5502 yaptığımda, şirketin bir başka müşterisi olan 'Ali Bey'in veya 'Ayşe Hanım'ın faturasını, adresini, T.C. kimlik numarasını görebiliyorsam; geçmiş olsun, nur topu gibi bir IDOR açığımız var.

Sadece URL Değil: JSON Gövdelerindeki Tehlike

Günümüzde artık her şey REST API'ler ve JSON üzerinden dönüyor. Haliyle IDOR da evrim geçirdi. Modern bir web uygulamasında profil güncelleme isteği şöyle görünebilir:

POST /api/users/update-profile HTTP/1.1
Host: api.testcompany.example.com
Content-Type: application/json
Authorization: Bearer <benim_tokenim>

{
  "userId": "1337",
  "email": "[email protected]",
  "bio": "Red Team Lead at testCompany"
}

Burada saldırgan (veya bizim meraklı çocuk), kendi token'ı ile isteği gönderiyor ama JSON içindeki userId değerini 1338 (kurbanın ID'si) yaparak başkasının profil bilgilerini güncelleyebiliyor. Sistem sadece token'ın geçerli olup olmadığına bakıyor, token sahibinin 1338 nolu kullanıcıyı değiştirme yetkisi olup olmadığını kontrol etmiyor.

Kodun İçindeki Karanlık: Nerede Hata Yapıyoruz?

Genellikle backend tarafında şöyle bir mantık hatasıyla karşılaşıyoruz (Pseudo-code):

# ZAFİYETLİ ÖRNEK (Python/Flask benzeri)
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
    # Kullanıcı login olmuş mu? Evet (@login_required kontrol etti)
    # Veriyi çek ve dön
    invoice = db.query("SELECT * FROM invoices WHERE id = ?", (invoice_id,)).first()
    
    if invoice:
        return jsonify(invoice)
    return "Bulunamadı", 404

Yukarıdaki kodda eksik olan ne? Kullanıcı oturum açmış olsa bile, o faturanın ona ait olup olmadığına bakılmıyor. Olması gereken yaklaşım ise 'Object Level Authorization' dediğimiz katmandır:

# GÜVENLİ ÖRNEK
@app.route('/api/invoice/<invoice_id>')
@login_required
def get_invoice(invoice_id):
    user_id = get_current_user_id() # Session veya Token'dan gelen güvenilir ID
    
    # Veriyi çekerken hem ID'yi hem de sahipliği kontrol et
    invoice = db.query("SELECT * FROM invoices WHERE id = ? AND owner_id = ?", 
                       (invoice_id, user_id)).first()
    
    if not invoice:
        # Güvenlik açısından 'Yetkin yok' yerine 'Bulunamadı' demek bazen daha iyidir (Enumeration'ı engeller)
        return "Fatura bulunamadı veya erişim yetkiniz yok", 404
        
    return jsonify(invoice)

UUID Kullanmak Bir Kurtuluş mu?

Birçok kişi bana şunu soruyor: 'Abi biz incremental (ardışık) ID kullanmıyoruz, UUID (v4) kullanıyoruz, yine de IDOR olur mu?'

Cevap: Evet, olur. UUID sadece tahmin edilebilirliği (predictability) ortadan kaldırır. Yani saldırgan 5501'den sonra 5502 gelmeyeceğini bilir. Ancak bir şekilde (loglardan, başka bir endpoint'ten veya sosyal mühendislikle) bir başkasının UUID'sini ele geçirirse ve senin kodunda 'yetki kontrolü' yoksa, o kapı yine ardına kadar açılır. UUID bir güvenlik duvarı değil, sadece 'tahmin etmeyi zorlaştıran' bir unsurdur. Güvenliğinizi 'gizliliğe' (security by obscurity) dayandıramazsınız.

Defans Hattını Nasıl Kurarız?

Eğer bir DevSecOps kültürünüz varsa veya Red Team bulgularını remediate etmeye çalışan bir ekipseniz, şu adımları check-list'inize ekleyin:

  1. Indirect Object References: Kullanıcıya doğrudan veritabanı ID'sini göstermek yerine, o oturuma özel map edilmiş geçici token'lar kullanabilirsiniz (Zordur ama çok güvenlidir).
  2. Yatay ve Dikey Yetki Kontrolü: Sadece 'Admin mi değil mi?' (Vertical) diye bakmayın. 'Bu kullanıcı, şu kullanıcının verisini görebilir mi?' (Horizontal) sorusunu her fonksiyonun başında sorun.
  3. BOLA Testlerini Otomatize Edin: CI/CD süreçlerinde Burp Suite, OWASP ZAP veya kendi yazdığınız scriptlerle API endpoint'lerini farklı kullanıcı token'ları ile çapraz sorgulayın.
  4. Logging ve Monitoring: Bir kullanıcı kısa sürede yüzlerce farklı ID için istek atıyorsa (Insecure Direct Object Reference denemesi), bunu alarm olarak alın. '403 Forbidden' veya '404 Not Found' sayısındaki ani artışlar genelde birilerinin kapıyı zorladığının kanıtıdır.

Son Söz Yerine

Web dünyası her geçen gün karmaşıklaşıyor ama hatalar hep aynı kalıyor. IDOR, aslında bir 'mantık' hatasıdır. Makineler mantığı hatasız çalıştırır ama mantığı kuran biz insanlarız. Kodu yazarken 'Buraya benim yerime en azılı düşmanım gelse neyi değiştirebilir?' diye bir saniye durup düşünmek, emin olun sizi manşetlere çıkmaktan kurtaracaktır.

Kendinize ve sistemlerinize iyi bakın. Terminaliniz her daim yeşil kalsın!

İlgili yazılar