API Yetkilendirme Hataları: IDOR'u UUID ile Çözdüğünü Sanma

API güvenliğinde en pahalı hata genelde authentication değil authorization tarafında çıkar. IDOR, tenant izolasyonu ve obje bazlı kontrol için pratik bir test planı.

4 dk okuma
ibrahimsql
616 kelime

API Yetkilendirme Hataları: IDOR'u UUID ile Çözdüğünü Sanma#

Bir API'de login çalışıyor diye kullanıcı doğru veriye erişiyor sanmak pahalı bir yanılgıdır. Gerçek güvenlik sorusu şudur: Bu kullanıcı bu objeyi, bu tenant içinde, bu aksiyonla gerçekten görmeli mi?

IDOR çoğu zaman id=123 değerini id=124 yapmak kadar karikatürize anlatılır. Üretimdeki açıklar daha sessizdir: tahmin edilemeyen UUID'ler, eksik tenant filtresi, admin panelinden kopyalanmış servis metodu veya frontend'de gizlenmiş ama API'de yaşayan aksiyonlar.

UUID tahmin edilebilirliği değil, yetki kararı önemlidir#

UUID kullanmak iyi bir hijyen olabilir ama yetkilendirme kontrolü değildir. Çünkü saldırganın objenin ID'sini öğrenmesi için tek yol brute force değildir.

ID şu yerlerden sızabilir:

  • E-posta bildirimleri
  • Export dosyaları
  • Log kayıtları
  • Referer header'ları
  • Paylaşılmış linkler
  • Mobil uygulama cache'i
  • GraphQL response içinde gömülü ilişkiler

ID bilindiği anda sistemin karar vermesi gerekir: "Bu kullanıcının bu kaynağa erişim hakkı var mı?" Cevap ID formatından değil, authorization katmanından gelmelidir.

Yetki kontrolünü controller'a serpmek açık üretir#

Kötü desen genelde şöyle görünür:

const invoice = await db.invoice.findUnique({ where: { id: invoiceId }, }) if (invoice.userId !== session.user.id) { throw new Error('Forbidden') }

Bu kod tek endpoint için çalışabilir. Problem, aynı invoice objesine başka bir endpoint'ten erişildiğinde başlar. PDF export, webhook retry, admin preview, support paneli ve mobil API aynı kontrolü tekrar tekrar yazmaya başladığında bir tanesi unutulur.

Daha güvenli desen, sorgunun kendisine sahiplik filtresi eklemektir:

const invoice = await db.invoice.findFirst({ where: { id: invoiceId, tenantId: session.tenantId, memberships: { some: { userId: session.user.id, role: { in: ['owner', 'finance'] }, }, }, }, }) if (!invoice) { throw new Error('Not found') }

Burada kritik fark şu: yetkisiz kaynak ile var olmayan kaynak aynı davranışı verir. Bu hem veri sızıntısını azaltır hem de test edilebilir bir sözleşme oluşturur.

Test planı: aynı kullanıcı değil, farklı ilişki dene#

IDOR testi sadece iki kullanıcı açıp ID değiştirmek değildir. İlişki modelini bozacak kombinasyonları denemek gerekir.

| Senaryo | Beklenen davranış | | --- | --- | | Aynı tenant, farklı rol | Rol aksiyona yetmiyorsa 403 veya 404 | | Farklı tenant, aynı obje tipi | Veri hiçbir şekilde dönmemeli | | Eski üyelik, aktif token | Yetki membership iptalinden sonra düşmeli | | Read yetkisi, write isteği | Yazma aksiyonu engellenmeli | | Soft-delete obje | Liste, detay ve export aynı davranmalı | | Admin route kopyası | Internal endpoint dışarıdan kullanılamamalı |

Bu tabloyu otomasyon testine çevirmek zor değildir. Zor olan, ürün büyürken her yeni endpoint'in bu matrise girmesini zorunlu hale getirmektir.

Tenant izolasyonu en sık include ile bozulur#

ORM kullanan ekiplerde sık gördüğüm hata, ana sorguda tenant filtresi varken ilişkili verinin fazla geniş çekilmesidir.

const project = await db.project.findFirst({ where: { id: projectId, tenantId: session.tenantId, }, include: { apiKeys: true, auditLogs: true, }, })

Bu kod yalnızca project seviyesinde doğru görünür. Ancak apiKeys veya auditLogs başka bir paylaşım modeline sahipse, ilişkiyi de ayrıca filtrelemek gerekir. "Parent doğruysa child da doğrudur" varsayımı, çok kiracılı sistemlerde güvenli değildir.

İyi kapanış kriteri: bulgu değil kök neden kapat#

Bir IDOR bulgusu kapatılırken sadece ilgili endpoint düzeltilirse aynı açık iki hafta sonra başka yerde çıkar. Daha iyi kapanış kriteri şudur:

  • Kaynak erişimi için tek bir policy fonksiyonu var mı?
  • Policy hem read hem write aksiyonlarını ayırıyor mu?
  • Tenant filtresi sorgunun içinde mi?
  • Yetkisiz ve var olmayan kaynaklar aynı dış davranışı veriyor mu?
  • Test matrisi yeni endpoint eklenirken çalışıyor mu?
  • Loglarda "kim, hangi objeye, hangi policy ile erişti" bilgisi var mı?

Kısa sonuç#

API güvenliğinde IDOR'u UUID ile değil, tutarlı authorization kararıyla kapatırsın. ID'ler sızabilir, frontend gizlediğin butonlar çağrılabilir, eski tokenlar beklenmedik yerden dönebilir. Güvenli tasarım, her istekte aynı soruyu sorar: Bu kullanıcı bu objeye bu aksiyonla gerçekten erişmeli mi?

---
Bu yazıyı paylaş:
TwitterLinkedInFacebook

Ne düşünüyorsun?

Tepki bırakarak geri bildirim ver