Eskiden web güvenliği denince aklımıza sadece SQL Injection ve XSS gelirdi. WAF'ı kurardık, inputları temizlerdik ve 'Tamamdır, bu gece rahat uyuruz' derdik. Ama artık dünya değişti. Kimse artık sadece 8 karakterli parolalara güvenmiyor, herkes 16-20 karakterli, içinde hiyeroglif gibi karakterler olan şifreler peşinde. Fakat asıl mesele şu: Kapıyı ne kadar sağlam kilitlersen kilitle, mimarinin kendisinde bir 'mantık hatası' varsa, saldırgan kapıyı değil duvarın kendisini yürütüp gider.
Bugün seninle, son yıllarda bizim Red Team operasyonlarında en çok ekmeğini yediğimiz, savunma tarafındakilerin ise genellikle 'Ya biz oraya zaten yetkilendirme koymuştuk' diyerek yanıldığı bir konuyu konuşacağız: API Güvenliği ve onun yaramaz çocuğu BOLA (Broken Object Level Authorization).
Monolitten Mikroservise: Ne Değişti?
Bak dostum, eskiden her şey sunucu tarafında (Server-Side) olup biterdi. Kullanıcı bir butona basar, sunucu her şeyi hesaplar ve hazır bir HTML render edip gönderirdi. Şimdi ise 'Headless' yapılar ve SPA'lar (Single Page Applications) hüküm sürüyor. Sunucu artık sadece ham veriyi (genellikle JSON formatında) API'ler üzerinden fırlatıyor. Görselliği ve mantığın bir kısmını ise senin tarayıcın (React, Vue, Angular vs.) hallediyor.
İşte tehlike burada başlıyor. Güvenlik kontrollerinin bir kısmının 'istemci tarafına' (Client-side) kayması, arka plandaki API uç noktalarının (endpoints) savunmasız kalmasına neden oluyor. Bir saldırgan olarak ben, tarayıcının bana gösterdiği arayüzle ilgilenmem. Ben, o arayüzün arkasında fink atan API isteklerine bakarım.
BOLA (IDOR): 'Sadece Bir Sayı Değiştirsem Ne Olur?'
BOLA, eski adıyla IDOR (Insecure Direct Object Reference), aslında çok basit bir mantığa dayanıyor: 'Benim bu veriyi görmeye yetkim var ama başkasınınkine de bakabilir miyim?'
Hayal et, testCompany adında bir e-ticaret sitesinde çalışıyorsun. Kendi profil sayfanı açtığında tarayıcının arkada şöyle bir istek attığını görüyorsun:
GET /api/v1/user/profile/1005 HTTP/1.1
Host: api.testcompany.com
Authorization: Bearer <senin_tokenin>
Burada 1005 senin kullanıcı ID'n. Bir siber güvenlik uzmanı (veya meraklı bir saldırgan) hemen şunu düşünür: 'Ben bu 1005'i 1006 yapsam ne olur?'
Eğer sistem sadece 'Bu kullanıcı giriş yapmış mı?' (Authentication) kontrolü yapıp, 'Bu kullanıcı 1006 ID'li veriyi görmeye yetkili mi?' (Authorization) kontrolünü yapmıyorsa, geçmiş olsun. Başkasının tüm özel bilgilerine eriştin demektir.
Sahadan Bir Senaryo: Mass Assignment Tehlikesi
Şimdi çıtayı biraz yükseltelim. BOLA'nın kuzeni olan 'Mass Assignment' konusuna gelelim. Bu, genellikle modern frameworklerin (Entity Framework, Hibernate, Eloquent vs.) geliştiricilere sağladığı 'kolaylıklardan' kaynaklanıyor. Geliştirici, kullanıcıdan gelen tüm JSON objesini doğrudan veritabanı modeline map ederse başı ağrıyabilir.
Örnek bir profil güncelleme isteği düşünelim:
// Giden İstek (Normal Şartlarda)
PATCH /api/v1/user/update/1005
{
"first_name": "Sedat",
"last_name": "Ozdemir",
"phone": "555-000-0000"
}
Red Team gözlüğüyle baktığımda, ben bu JSON'a fazladan bir alan (field) eklemeyi denerim. Mesela sistemde is_admin veya role gibi bir alan olup olmadığını tahmin etmeye çalışırım:
// Zararsızlaştırılmış Saldırı Payload'u
PATCH /api/v1/user/update/1005
{
"first_name": "Sedat",
"last_name": "Ozdemir",
"is_admin": true,
"role": "super_admin",
"account_balance": 999999
}
Eğer backend tarafında 'allow-list' (izin verilen alanlar listesi) kullanılmıyorsa ve gelen her şeyi olduğu gibi kaydediyorsa, bir saniye içinde kendimi admin yapabilirim. İşte bu, 'kapıyı kilitleyip pencereleri açık bırakmak' tam olarak budur.
Otomasyonun Gücü ve Görünmezlik
Tabii ki biz bu işleri elle 1001, 1002, 1003 diye denemiyoruz. Elimizde çok güzel araçlar var ama bazen basit bir Python scripti bile hayat kurtarır. Aşağıda, savunma tarafında kendi API'lerinizi test etmeniz için 'zararsızlaştırılmış' bir probe mantığı bırakıyorum:
import requests
# EĞİTİM AMAÇLI: Kendi sistemindeki bir açığı test etmek için
def check_bola(target_id):
url = f"https://api.testcompany.com/v1/data/{target_id}"
headers = {
"Authorization": "Bearer MOCK_USER_TOKEN_HERE",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
print(f"[!] Potansiyel BOLA Bulgusu: ID {target_id} içeriği erişilebilir!")
# Verinin içeriğini kontrol et, sana mı ait yoksa başkasına mı?
else:
print(f"[-] ID {target_id}: Erişim reddedildi.")
# 127.0.0.1 üzerindeki lab ortamında test ediyoruz
for i in range(1000, 1010):
check_bola(i)
Nasıl Korunacağız? (Defensive Mindset)
Bak dostum, sızmak eğlenceli ama asıl sanat savunmakta. Bu zafiyetlerden kaçınmak için şu 3 kuralı aklına kazı:
- Güvenlik Mantığını Merkezi Hale Getir: Her API endpoint'inin içinde ayrı ayrı 'bu kullanıcı buna yetkili mi?' kontrolü yazarsan, bir yerde mutlaka unutursun. Yetkilendirme (Authorization) mekanizması merkezi bir katmanda (Middleware) çözülmeli.
- Asla Doğrudan Veritabanı ID'si Kullanma: URL'de
/user/1005yerine/user/u_8f2a-b9c1-4e7dgibi tahmin edilemez UUID'ler kullan. Bu, saldırganın 'bir sonrakini deneyeyim' ihtimalini ortadan kaldırır. Ancak dikkat: Bu sadece bir 'obscurity' (gizleme) yöntemidir, gerçek bir yetkilendirme kontrolü yerine geçmez! - DTO (Data Transfer Object) Kullan: Gelen isteği (Request) doğrudan veritabanı modeline bağlama. Kullanıcının hangi alanları değiştirebileceğini tanımlayan sınıflar (DTO) kullan.
is_adminalanı asla bu DTO içinde olmasın.
Son Söz Yerine
Siber güvenlik bir 'koşu' değil, bir 'maraton'. Biz Red Team olarak her zaman en zayıf halkayı arıyoruz. Modern web dünyasında bu halka artık karmaşık şifreleme algoritmaları değil, gözden kaçan basit bir mantık kontrolü oluyor.
Bir dahaki sefere bir API yazarken veya test ederken kendine şu soruyu sor: 'Eğer kullanıcı bu isteği modifiye ederse, ona istemediği bir şeyi verir miyim?' Cevabın 'belki' bile olsa, o kodu tekrar gözden geçir.
Terminalde kalın, güvende kalın!
