Selam ekip, kahveler hazırsa bugün biraz 'modern' dertlerimizden konuşalım. Siber güvenlikte trendler öyle bir hızla değişiyor ki; artık kimse sadece 8 karakterli parolaları kırmakla veya klasik SQL Injection aramakla vakit kaybetmiyor. Eskiden bir 'WAF' (Web Application Firewall) kurup arkamıza yaslanırdık ama artık o devir kapandı. Neden mi? Çünkü saldırganlar artık kapıyı kırmak yerine, kapının üzerindeki o küçük ama ölümcül mantık hatasını arıyor.
Bugün 'testCompany' üzerinden bir senaryo ile, API dünyasının kanayan yarası olan Broken Object Level Authorization (BOLA) nam-ı diğer IDOR ve iş mantığı hatalarının derinliklerine ineceğiz. Hazırsanız, terminali açalım.
Artık SQL Injection Değil, Mantık Hataları Konuşuyor
Trend şuraya evrildi: Yazılım ekipleri inanılmaz hızlı kod yazıyor, 'Microservices' mimarileri havada uçuşuyor ama bu hızın getirdiği bir bedel var. API uç noktaları (endpoints) kontrolsüzce büyüyor. Bir Red Teamer olarak söyleyebilirim ki; modern bir web uygulamasında en büyük zafiyet artık koddaki bir karakter hatası değil, 'Bu veriyi kim görmeli?' sorusunun yanlış cevaplanmasıdır.
Senaryo: testCompany'nin 'Kusursuz' API'si
Düşünün ki testCompany isimli bir girişimdeyiz. Geliştirici arkadaşımız 'Can', kullanıcıların kendi profil bilgilerini görmesi için harika bir endpoint yazdı:
GET /api/v1/user/profile/1005
Buradaki 1005 bizim user_id değerimiz olsun. Burp Suite'i açıp araya girdiğimizde giden isteği ve gelen cevabı görelim:
İstek (Request):
GET /api/v1/user/profile/1005 HTTP/1.1
Host: api.testcompany.example.com
Authorization: Bearer <valid_token_for_user_1005>
Cevap (Response):
{
"id": 1005,
"username": "can_dev",
"email": "[email protected]",
"address": "Istanbul, TR",
"secret_note": "Sunucu şifresi: admin123"
}
Buraya kadar her şey normal görünüyor. Peki, Can bu endpoint'i yazarken arkada şu kontrolü yaptı mı? "Gelen Token 1005 nolu kullanıcıya ait, ama bu istek 1006 nolu kullanıcı için gelirse ne yapmalıyım?"
Çoğu zaman yapılan hata şudur: 'Token geçerli mi? Evet. O zaman veriyi getir.' İşte bu noktada saldırgan (veya biz Red Team ekibi) devreye giriyor. Parametreyi değiştiriyoruz:
GET /api/v1/user/profile/1006
Eğer sistem bize 1006 nolu kullanıcının verilerini de döküyorsa, tebrikler; elinizde nur topu gibi bir BOLA (Broken Object Level Authorization) zafiyeti var.
İş Mantığı (Business Logic) Hatalarının Karanlık Yüzü
Sadece ID değiştirmekle bitmiyor olay. Bazen iş daha 'geek' bir hal alıyor. Bir e-ticaret uygulamasını düşünün. Sepete ürün ekliyoruz.
Zararsızlaştırılmış Payload Örneği:
POST /api/v1/cart/add HTTP/1.1
Host: shop.testcompany.example.com
Content-Type: application/json
{
"product_id": "99",
"quantity": 1,
"price": 5000
}
Burada saldırganın gözleri parlar. 'Fiyat bilgisini neden istemci (client) gönderiyor?' der. Hemen müdahale eder:
{
"product_id": "99",
"quantity": 1,
"price": 1
}
Eğer backend, veritabanındaki fiyat yerine gelen JSON'daki fiyata güveniyorsa, 5000 TL'lik o ekran kartını 1 TL'ye aldınız demektir. Bu bir zafiyet değil, bu bir 'mantık' felaketidir.
Defansif Reçete: Nasıl Korunuruz?
"Sedat abi iyi güzel de, biz bu işten nasıl kaçarız?" dediğinizi duyar gibiyim. Mavi takım (Blue Team) ve geliştirici dostlarım için bir liste hazırladım:
UUID (Universally Unique Identifier) Kullanın: Ardışık (incrementing) ID'ler (1001, 1002...) saldırgan için 'tahmin edilebilir' bir hedeftir. Bunun yerine
550e8400-e29b-41d4-a716-446655440000gibi karmaşık UUID'ler kullanmak, tahmin saldırılarını (fuzzing) imkansız hale getirir. Ama dikkat! UUID güvenlik sağlamaz, sadece 'gizlilik katmanı' ekler (security by obscurity).Nesne Seviyesinde Yetki Kontrolü (Resource-Based Authorization): Sadece 'Kullanıcı giriş yapmış mı?' diye sormayın. 'Bu kullanıcı, talep ettiği ID'ye sahip nesnenin SAHİBİ mi?' diye sorun. Kod bloğunuz şuna benzemeli (pseudo-code):
# Kötü Uygulama def get_user_profile(requested_id): user_data = db.query("SELECT * FROM users WHERE id = ?", requested_id) return user_data # İyi Uygulama (Defensive) def get_user_profile(current_session_user_id, requested_id): if current_session_user_id != requested_id: log_security_event("Unauthorized access attempt!") return abort(403) # Yetkisiz erişimi engelle user_data = db.query("SELECT * FROM users WHERE id = ?", requested_id) return user_dataİstemci Verisine Asla Güvenmeyin: Fiyat, kullanıcı rolü, indirim oranı gibi kritik bilgileri asla client-side'dan (frontend) almayın. Bunları backend'de veritabanından çekin.
Mass Assignment Zafiyetine Dikkat: Bir profil güncelleme isteğinde sadece
emailalanını güncellemek isterken, tüm objeyi veritabanına kaydederseniz; saldırgan araya girip"is_admin": trueparametresini ekleyebilir.Zararlı Görünen Ama Etkisiz Payload Örneği:
PUT /api/v1/user/update { "email": "[email protected]", "is_admin": true }Bunu engellemek için sadece izin verdiğiniz alanları (allow-list) kabul edin.
Red Team Bakış Açısıyla Bir Tavsiye
Biz sızma testlerinde veya Red Team operasyonlarında otomatik tarayıcılara çok güvenmeyiz. Neden biliyor musunuz? Çünkü bir DAST (Dynamic Application Security Testing) aracı, iş mantığını anlayamaz. Sepetteki fiyatın 1 TL olmasının 'yanlış' olduğunu bilemez. Bu yüzden manuel testler ve mantık akış şemaları üzerinden gitmek hayat kurtarır.
Siz de sistemlerinizi geliştirirken sadece 'Hata veriyor mu?' diye değil, 'Bu akışta bir tuhaflık yapabilir miyim?' diye düşünün. Unutmayın, en güvenli kod, yazılmayan kod değildir; her adımı sorgulanan koddur.
Bir sonraki yazıda API gateway'lerin derinliklerine ve JWT (JSON Web Token) manipülasyonlarına dalarız. O zamana kadar, loglara iyi bakın, 'unauthorized' istekleri kaçırmayın!
Selametle.
