İçeriğe atla
0 XP 0
11 dkderinleşmeyle 12 dk50 XP5 etkinlik

Nesne düzeyi yetki: IDOR ve BOLA

Bu derste
  • Nesne düzeyi yetki hatasını (IDOR ve BOLA) kök nedeninden tanı, sızıntıyı erişim kaydından yakala, sahiplik denetimini sorgunun içine koyarak kapat.
  • Nesne düzeyindeki yetki hatasının nedenini bul. Kimlik biçimlerini ayırt et ve sızıntıyı kayıtlardan tanı. Sunucuda sahiplik kontrolü yaparak açığı kapat.

Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +50 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.

Web isteği iki ayrı soru taşır. Kimlik doğrulama kim olduğunu sorar ve girişte bir kez cevaplanır. Erişim denetimi (access control) ise şu kayda dokunmaya hakkın var mı diye sorar ve her istekte yeniden sorulmalıdır. Bu derste ikinci sorunun atlandığı yerde doğan nesne düzeyi yetki hatasını ele alırsın.

Önce kusurun kök nedenini bulur, kimliğin hangi biçimlerde geldiğini ayırt eder, sızıntıyı erişim kaydından tanır ve sahiplik denetimini sorgunun içine koyarak kapatırsın. Kural yalnız okuma uçları için değil, yazma uçları ve dosyalar için de geçerlidir.

Kimlik değil yetki: kusurun kökü

Nesne düzeyinde yetki hatası, istekteki kayıt numarasının değiştirildiği ve sunucunun numaraya bakıp sahibine bakmadığı yerde doğar. Bu sınıfın uygulama programlama arayüzü (API) dünyasındaki adı BOLA'dır (Broken Object Level Authorization, nesne düzeyi yetki kırılması). Web dünyasındaki eski adı IDOR'dur (Insecure Direct Object Reference, güvensiz doğrudan nesne başvurusu). İki ad da aynı kusuru gösterir: sunucu, istemciden gelen kayıt numarasına güvenir ve isteği yapan kişiyi sorgulamadan kaydı teslim eder.

Numara adres çubuğundan, istek gövdesinden ya da gizli bir form alanından gelebilir; üçü de kullanıcının elindedir. OWASP Top 10 2025 listesinde bozuk erişim denetimi ailesi (A01) birinci sıradadır ve denetlenen uygulamaların tamamında bir biçimde görülmüştür. Nesne düzeyi hata, OWASP API Güvenliği listesinde ilk maddedir ve orada yaygınlığı en üst düzeyde gösterilir.

// Güvensiz: oturum var, sahiplik sorulmuyor
app.get("/api/gonderiler/:id", oturumGerekli, async (req, res) => {
  const g = await db.gonderi.findUnique({ where: { id: Number(req.params.id) } });
  res.json(g);
});
Altay Kargo müşteri portalının gönderi ucu. oturumGerekli yalnız girişi denetler.

oturumGerekli ara katmanı yalnız ilk soruyu, yani girişi denetler. Sorgu ise kaydı yalnız numarasına göre çeker ve isteği kimin yaptığına bakmaz. Bu uç, giriş yapmış her kullanıcıya her gönderiyi açar.

Eksiklik koda değil, koddaki bir varsayıma dayanır: geliştirici, kullanıcının yalnız kendi numarasını göndereceğini varsayar. Saldırgan bu varsayımı ilk denemede bozar. findUnique kaydı tek anahtara göre bulur; o anahtar da doğrudan istemciden gelen numaradır.

# 2291 numaralı müşterinin oturumu, önce kendi gönderisi
GET /api/gonderiler/55120 HTTP/1.1
Host: portal.altaykargo.example
Cookie: oturum=7Qe1...

HTTP/1.1 200 OK
{"id":55120,"musteriId":2291,"alici":"Selin T.","il":"Ankara"}

# Aynı oturum, yalnız numara bir artırıldı
GET /api/gonderiler/55121 HTTP/1.1
Host: portal.altaykargo.example
Cookie: oturum=7Qe1...

HTTP/1.1 200 OK
{"id":55121,"musteriId":1450,"alici":"Ayşe K.","il":"İzmir","telefon":"05** *** ** 21"}
Burp Repeater'da aynı istek iki kez yollandı; yalnız adresteki numara değişti.

İki istekte çerez, yani oturum, aynıdır; değişen tek şey adresteki numaradır. İlk yanıt beklenen sonuçtur: 55120 numaralı kaydın sahibi 2291'dir ve isteyen de 2291'dir.

İkinci yanıtta musteriId 1450 olur. Kaydın sahibi ile oturumun sahibi farklı olduğu halde sunucu 200 dönmüştür; kusurun kanıtı bu iki satırdır. Sahiplik denetimi olsaydı ikinci istek 404 ya da 403 alırdı. Hata bu kadar basit olduğu için çoğu zaman sıradan bir kullanıcı bile adresteki numarayı merakla değiştirip bulur.

Testte Burp Suite'in Repeater aracıyla isteği yakalar, yalnız numarayı değiştirip yeniden yollar ve yanıtları yan yana koyarsın. Kanıt için iki isteği ve yanıtı saklar, raporda kişisel veriyi maskelersin. Denemeyi kendi açtığın iki test hesabıyla yaparsın. Bunu yalnız kendi lab ortamında ya da yazılı iznin olan hedefte dene.

Etkinlik 1 / 5

Aşağıdaki uçta sahiplik denetimini yapan koşul eksik. Hatalı satırı bul.

Zafiyetli satıra tıkla.

Kimliğin dört biçimi

Kayıt numarası her zaman düz bir sayı değildir. Dört biçimde karşına çıkar ve her birinin tespit yolu farklıdır. Birincisi artan sayıdır; 55120, 55121 diye sırayla gider ve tahmini en kolay olandır.

İkincisi kodlanmış kimliktir; sunucu numarayı Base64 ile sarar, adreste anlamsız görünür. Base64 şifreleme değildir, geri çözülür. Üçüncüsü özetlenmiş kimliktir; numara bir özet işlevinden (hash) geçirilir, ama özet artan bir sayıdan üretiliyorsa aynı sırayla yeniden hesaplanabilir.

Dördüncüsü tahmin edilemez kimliktir; örneğin rastgele üretilen bir UUID. Bunu sırayla denemek işe yaramaz. Tespit yöntemi biçime göre değişir: artan sayıyı doğrudan denersin, Base64'ü çözüp içindeki sayıyı görürsün, özeti bilinen değerlerle karşılaştırırsın. Dördünde de asıl soru aynıdır: sunucu sahipliği sorguda denetliyor mu?

# Adres çubuğundaki kimlik anlamsız görünebilir
GET /api/gonderiler/Z29uZGVyaTo1NTEyMQ==

# Base64 çözülür, içindeki numara ortaya çıkar
$ echo 'Z29uZGVyaTo1NTEyMQ==' | base64 -d
gonderi:55121
Kodlanmış kimlik yalnız gizler, denetlemez. Bir sonraki kayıt Z29uZGVyaTo1NTEyMg== ile istenir.

Kimlik yalnız tek bir kaydın adresinde durmaz. İç içe kaynaklarda da geçer: bir siparişin altındaki kalemler, bir kullanıcının altındaki adresler. /api/musteriler/88/adresler/12 gibi bir yolda hem müşteri hem adres numarası istemciden gelir.

Sunucu ikisinin birbirine ait olduğunu ayrıca doğrulamazsa, saldırgan kendi müşteri numarasının altına başka bir adres numarası yazar ve komşusunun adresini kendi hesabının içinden okur. Toplu uçlar da risklidir: bir kimlik listesini tek istekte gönderip birçok kaydı birden çeken bir uç, sahiplik filtresini liste boyunca tek tek uygulamazsa aradaki yabancı numaraları da döndürür. Bu yüzden denetim tek bir kayıt için değil, isteğin taşıdığı her kimlik için yapılır.

Kodlanmış ya da özetlenmiş kimlik bir güvenlik önlemi gibi görünür, ama yalnız bir görünüm katmanıdır. Kimlik bir e-postada, bir kayıt satırında ya da paylaşılan bir bağlantıda sızarsa denetimsiz uç yine açılır. Rastgele UUID tahmini zorlaştırır, bu doğru. Yine de tek başına yetki denetiminin yerini tutmaz.

Doğru yaklaşım kimliği gizlemeye çalışmak değil, sahipliği her istekte sorgunun içinde denetlemektir. Kimlik ne kadar karmaşık olursa olsun, denetim yoksa kapı açıktır. Bu yüzden kimliğin biçimi bir savunma değil, olsa olsa saldırının maliyetini artıran bir ayrıntıdır.

Etkinlik 2 / 5

Kayıt kimliklerini tahmin edilebilirliğine göre ayır.

Önce bir seçeneğe, sonra aşağıdaki uygun kutuya bas. İstersen seçeneği kutuya sürükleyip bırak. Yerini değiştirmek için aynı işlemi tekrarla.

0/5 seçenek yerleşti.

Taramayı erişim kaydından görmek

Bu hatanın izi erişim kaydında (access log) nettir. Saldırgan numarayı elle denemez, bir listeyi betikle sırayla dener. Bu yüzden istekler saniyeler içinde art arda düşer ve gittikleri kayıt numaraları birbirine yakındır. Tek bir oturumdan ya da tek bir adresten çıkan bu ardışık akış, normal bir kullanıcının davranışına benzemez.

Sıradan kullanıcı kendi birkaç kaydını açar ve arada durur. Otomatik tarama ise kısa sürede yüzlerce numarayı yoklar. Yanıt kodları da ipucu verir: sahipsiz kayıtta 404 ya da 403, sızan kayıtta 200 döner.

Yanıt kodunu okumak taramanın sonuç verip vermediğini söyler. Art arda 404 gören bir tarama henüz bir şey sızdırmamıştır; sunucu kaydı ya bulamıyor ya da sahiplik denetiminde durduruyordur. Arada bir 200 çıkması ise o numaranın sahipsiz biçimde açıldığını gösterir ve asıl alarmı orada verirsin.

İki şeyi birlikte izle: hem denemenin hacmini hem de dönen kodların dağılımını. Yalnız 200'lere bakmak, henüz sonuç vermemiş ama devam eden bir taramayı gözden kaçırır. Erken tespit, sızıntı olmadan önce kapıyı kapatma şansını verir.

Tespiti bir kurala bağlamak da mümkündür. Aynı oturumdan ya da aynı adresten kısa bir pencerede belli bir eşiği aşan sayıda farklı kayıt numarasına gidilmesi, iyi bir alarm koşuludur. Buna 404 oranını da eklersin: normal kullanıcı nadiren 404 alır, tarama ise çoğunlukla 404 toplar.

İki sinyal birlikte yanlış alarmı azaltır. Alarmı yalnız sızıntı olduktan sonra değil, deneme başladığı an kurmak istersin; çünkü sıralı tarama sonuç verene kadar sessizce sürebilir. Web uygulama güvenlik duvarı bu kuralı isteğin önünde uygulayıp hız kesebilir, ama asıl düzeltme yine sunucudaki sahiplik denetimidir; duvar zaman kazandırır, kapıyı kapatmaz.

Etkinlik 3 / 5

Portalın erişim kaydında nesne düzeyi tarama izini işaretle.

Şüpheli satırlara tıkla. Birden fazla olabilir.

Çözüm: sahiplik sorgunun içinde

Düzeltme üç kurala dayanır. Birincisi, sorgu kaydı hem numarasına hem oturumdaki kullanıcıya göre çeker. İkincisi, kayıt bulunamazsa 404 döner; kaydın var olup olmadığı bile belli edilmez. 403 dönmek şu kayıt var ama senin değil bilgisini sızdırır, 404 bunu da kapatır.

Üçüncüsü, aynı kural yazma uçları için de geçerlidir. PUT, PATCH ve DELETE de sahiplik filtresi olmadan bırakılmaz. Okuma ucunu kapatıp yazma ucunu unutmak, kaydı silme ya da değiştirme yönünden yine açık bırakır. Rastgele kimlik bu üç kuralın yerini tutmaz, olsa olsa yanlarına eklenir.

// Güvenli: sahiplik sorgunun içinde
app.get("/api/gonderiler/:id", oturumGerekli, async (req, res) => {
  const g = await db.gonderi.findFirst({
    where: { id: Number(req.params.id), musteriId: req.oturum.kullaniciId },
  });
  if (!g) return res.status(404).end();
  res.json(g);
});
Başka müşterinin kaydı istenirse sorgu boş döner, yanıt 404 olur.

Güvenli sürümde findFirst hem id hem musteriId koşuluna bakar. Başka bir müşterinin numarası istenirse sorgu boş döner ve uç 404 ile yanıtlar. Kayıt sahibinin kim olduğu istemciye hiç açılmaz. Aynı deseni her fiil için tekrarlarsın.

Silme ucunda da sorgu oturumdaki kullanıcıyı koşula katar; katmazsa saldırgan kendi kaydı gibi görünen bir istekle komşusunun kaydını siler. Tek bir uçta unutulan koşul, bütün kayıt kümesini açar. Bu yüzden filtre koda tek tek değil, ortak bir yardımcı işlevle yazılır.

Etkinlik 4 / 5

Yukarıdaki güvenli sürümde, başka bir müşterinin kayıt numarası istenince sunucu ne döner?

Yazma uçları, dosyalar ve test disiplini

Aynı kök neden dosyalarda da görülür. Portal, faturaları rastgele adlı dosyalar olarak statik bir klasörden sunabilir. Dosya adı ne kadar uzun ve rastgele olursa olsun, adı bilen herkes dosyayı açar.

Bağlantı bir e-postayla iletilmişse ya da tarayıcı geçmişinde kalmışsa, adres artık gizli değildir. Burada da sahiplik denetimi yoktur. Kalıcı çözüm, dosyayı doğrudan klasörden değil, sahiplik denetimli bir uçtan sunmaktır.

Uç, oturumdaki kullanıcıyı faturanın sahibiyle her istekte karşılaştırır ve eşleşmezse 404 döner. Statik klasör yalnız herkese açık dosyalar için kullanılır.

Derinleş:885 milyon belge, tek haneli değişiklik

2019'da bir tapu sigortası şirketinin belge portalı, kamuoyunda geniş yer bulan bir nesne düzeyi yetki olayıyla anıldı. Portal, işlem belgelerini adresteki bir belge numarasıyla sunuyordu. Numara sırayla artıyordu ve bağlantıyı alan biri numaradaki tek haneyi değiştirerek başka müşterilerin belgelerine ulaşabiliyordu. Belgeleri görmek için hiçbir giriş gerekmiyordu.

Açık kayıtların en eskisi 2003'e kadar iniyordu ve toplamda yaklaşık 885 milyon belge etkilendi. Sızan içerikte banka hesap numaraları, ipotek kayıtları, kimlik numaraları ve sürücü belgesi görüntüleri vardı. Olay güvenlik gazeteciliğiyle ortaya çıktıktan sonra dış erişim kapatıldı. Ders açık: kimliğin sırayla artması ve sahiplik denetiminin hiç olmaması bir araya gelince, tek bir haneyi değiştirmek koca bir arşivi açar.

Denetimi testle kilitlersin. Her uç için en az iki durumu dener: sahibi isteyince 200, başka bir kullanıcı isteyince 404. Rol isteyen bir uçta müşteri ve kurye oturumuyla ayrı ayrı deneme yaparsın.

Yeni bir uç eklenip bu iki test yazılmazsa, unutulan sahiplik denetimi ilk orada ortaya çıkar. Bu alışkanlık, kontrol eklemeyi kod yazarken kendiliğinden getirir. Test kırmızı kaldıkça uç yayına çıkmaz. Böylece güvenlik, sonradan yapılan bir denetim değil, günlük geliştirme akışının bir parçası olur.

Derinleş:İki oturumu yan yana koymak

Nesne düzeyi hatayı elle aramak yorucudur; her uç için iki ayrı oturumla istek atıp yanıtları karşılaştırman gerekir. Sızma testçileri bunu araçla ölçekler. Burp Suite için yazılmış Autorize eklentisi, yüksek yetkili bir kullanıcının trafiğini izler ve aynı istekleri düşük yetkili ya da oturumsuz bir bağlamda otomatik olarak yeniden yollar.

Yanıtları karşılaştırıp her uca bir durum verir: denetim uygulanmışsa Enforced, atlanmışsa Bypassed, kararsızsa elle ayar isteyen bir uyarı. Aynı fikri kendi projene taşıyabilirsin. İki oturumla çalışan otomatik bir yetki testini sürekli tümleştirme hattına koyarsan, sonradan eklenen her uç bu farkı kendiliğinden gösterir. Böylece yetki denetimi bir kod inceleme konusu olmaktan çıkıp bir gerileme testine (regression test) dönüşür.

Nesne düzeyi hata tek bir kaydı hedef alır; saldırgan yalnız numarayı değiştirir ve uygulamanın geri kalanına dokunmaz. Bir sonraki derste aynı kök nedenin tek bir işlevin ya da tüm bir yönetim panelinin önünü nasıl açtığını göreceksin. Orada saldırgan numarayı değil, çağırdığı işlevin ya da yolun kendisini değiştirir. İki hata da tek bir cümleye çıkar: sunucu istemciye fazla güvendi.

Nesne düzeyinde bu güven bir kayıt numarasına, fonksiyon düzeyinde ise bir yola duyulur. İkisini de kapatan şey aynıdır: kararı istemciden alıp sunucuya vermek ve her istekte yeniden sormak.

Etkinlik 5 / 5

Doğru mu, yanlış mı?

Nesne düzeyi sahiplik denetimi, kaydı numarasına ek olarak oturumdaki kullanıcıya göre de arayan bir sorguyla kurulur.

Bu derste bir hata mı gördün? Bildir