Yazılar

IDOR (Insecure Direct Object Reference) Açığı Nedir? Authentication vs Authorization Farkı ve Çözümü

Web sitendeki /fatura/1042 adresini 1043 yaptığında başka bir müşterinin faturasını görüyorsan sisteminde OWASP API Top 10'un 1 numaralı açığı olan IDOR (BOLA) var demektir. Sadece UUID kullanmanın neden yetmediğini ve güvenli sorgu mimarisini inceliyoruz.

Furkan Özay24 Ekim 2026 · 3 dk okuma
czCZAY.DEV // EDITORIALTR
GÜVENLIK

#Güvenlik · 3 DK OKUMA

FURKAN ÖZAYVOL. 2026 // #C61D

Bir e-ticaret sitesine veya SaaS uygulamasına giriş yaptığını ve kendi faturanı görüntülemek için tarayıcıda şu adrese gittiğini düşün:

https://ornek-site.com/api/faturalar/1042

Peki adres çubuğundaki 1042 sayısını silip yerine 1043 yazdığında, ekranda hiç tanımadığın başka bir müşterinin adı, açık ev adresi, telefon numarası ve sipariş tutarı çıkıyorsa ne olur?

İşte siber güvenlik dünyasında OWASP API Security Top 10 listesinin 1 numaralı koltuğunda oturan en yaygın ve en tehlikeli zafiyetle tanıştın: IDOR (Insecure Direct Object Reference), yani güncel adıyla BOLA (Broken Object Level Authorization — Kırık Nesne Düzeyinde Yetkilendirme).

· · ·

1. IDOR Neden Olur? Authentication ve Authorization Karışıklığı

Hemen her backend geliştirici, özel bir API uç noktası (endpoint) yazarken başına "Kullanıcı giriş yapmış mı?" kontrolünü (authMiddleware veya JWT doğrulaması) koymayı hatırlar. Ancak çoğu zaman iki temel kavram birbirine karıştırılır:

  • Authentication (Kimlik Doğrulama — 401 Unauthorized): "Sen kimsin? Geçerli bir hesabın ve oturumun var mı?"
  • Authorization (Yetkilendirme — 403 Forbidden): "Evet, giriş yapmışsın; peki 1043 numaralı faturayı görmeye yetkin var mı, bu veri gerçekten sana mı ait?"

Hatalı (Zafiyetli) Backend Kodu

typescript
// ❌ ZAFİYETLİ KOD: Sadece giriş yapılıp yapılmadığına (requireAuth) bakıyor!
app.get("/api/faturalar/:id", requireAuth, async (req, res) => {
  const fatura = await db.faturalar.findUnique({
    where: { id: Number(req.params.id) },
  });
 
  return res.json(fatura);
});

Bu kodda saldırgan kendi gerçek hesabıyla giriş yaptığı için requireAuth kontrolünden başarıyla geçer! Ardından basit bir döngü (for id = 1 to 100000) yazarak sistemdeki tüm müşterilerin faturalarını birkaç dakika içinde bilgisayarına indirebilir.

· · ·

2. Büyük Yanılgı: "Biz Sıralı ID Yerine UUID Kullanıyoruz, Güvendeyiz!"

Pek çok geliştirici, veritabanındaki 1, 2, 3 gibi sıralı tam sayı (auto-increment integer) ID'leri f47ac10b-58cc-4372-a567-0e02b2c3d479 gibi rastgele UUID'lerle değiştirdiğinde IDOR açığını çözdüğünü sanır.

Uyarı: Gizleyerek Güvenlik (Security by Obscurity) Olmaz!

UUID kullanmak saldırganın ID'leri sırayla tahmin etmesini (brute-force) engeller, ancak IDOR açığını kapatmaz! Kullanıcı veya nesne UUID'leri çoğu zaman paylaşım linklerinde, genel yorumlarda, tarayıcı geçmişinde veya başka bir API yanıtında dışarı sızar. Eğer arka planda sahiplik kontrolü yoksa, o UUID'yi bir şekilde öğrenen saldırgan yine başkasının verisini okuyabilir veya silebilir!

· · ·

3. Gerçek ve Kalıcı Çözüm: Sorgu Katmanında Sahiplik Kontrolü

Kullanıcıdan gelen req.params.id veya req.body.id değerine asla tek başına güvenme. Veritabanına attığın her sorguda, istenen kaydın ID'sini oturum açmış kullanıcının kendi ID'siyle (req.user.id) mutlaka eşleştir:

typescript
// ✅ GÜVENLİ KOD: Hem fatura ID'si hem de oturumdaki kullanıcı ID'si aynı anda sorgulanır!
app.get("/api/faturalar/:id", requireAuth, async (req, res) => {
  const fatura = await db.faturalar.findFirst({
    where: {
      id: req.params.id,
      userId: req.user.id, // <- Nesne Düzeyinde Yetkilendirme (Ownership Check)
    },
  });
 
  if (!fatura) {
    // 404 dönmek, saldırganın o ID'de bir kayıt olup olmadığını bile anlamasını engeller
    return res.status(404).json({ error: "Fatura bulunamadı." });
  }
 
  return res.json(fatura);
});

Ekstra Savunma Hattı: PostgreSQL Row-Level Security (RLS)

Eğer çok kiracılı (multi-tenant) bir SaaS uygulaması geliştiriyorsan, bu kontrolü sadece uygulama koduna bırakmak yerine doğrudan PostgreSQL Row-Level Security (RLS) politikalarıyla veritabanı motoru seviyesinde de zorunlu kılabilirsin. Böylece bir yazılımcı kodda where: { userId } yazmayı unutsa bile veritabanı başka bir kullanıcının satırını döndürmeyi reddeder.

· · ·

İlgili Kısa Video & Kaynaklar

İnteraktif Yol Haritası & Araç

Bunu da oku