İçeriğe geç
Sedat Özdemir
Yazılar

api-security

API Kapılarını Açık Unutmak: Her JWT Seni Kurtarmaz mı Sandın?

JWT'n var, HTTPS kullanıyorsun, her şey yolunda gibi görünüyor. Peki ama bir kullanıcı neden başkasının faturasını görebiliyor? API dünyasının en sinsi hatasını, BOLA'yı masaya yatırıyoruz.

Sedat Özdemir
· 4 dk okuma

Kendi yazdığın API'nin, senin ruhun bile duymadan tüm veritabanını internete servis ettiğini fark ettiğinde o soğuk terlerin boğazından aşağı süzüldüğünü hissettin mi? Hani şu 'Abi her şeyi kontrol ettik, token kontrolü var, login olmayan giremez' dediğimiz o meşhur güven duygusundan bahsediyorum. İşte o duygu, genellikle Red Team ekibinin senin API'ndeki bir parametreyi değiştirip 100 bininci kayda ulaştığı saniyede tuzla buz oluyor.

Bugün seninle kahve eşliğinde, modern web dünyasının en sinsi, en 'göz önünde ama görünmez' belasından konuşacağız: Broken Object Level Authorization (BOLA) veya eski dostumuzun adıyla IDOR. Ama öyle teorik tanımlarla değil, sahada başımıza gelenlerle, testlerde canımızı sıkan o mantık hatalarıyla.

Kapıyı Kilitlemek Yetmez, İçerideki Çelik Kasayı da Kontrol Etmelisin

Genelde şöyle bir yanılgıya düşüyoruz: 'Kullanıcı giriş yaptı mı? Yaptı. Elinde geçerli bir JWT (JSON Web Token) var mı? Var. O zaman bu isteğe cevap verebiliriz.' İşte bu, güvenliğin 'check-box' mantığıyla yönetilmeye çalışılmasının en büyük kurbanıdır.

Bir senaryo düşünelim. testCompany adında bir fintech girişiminde çalışıyorsun. Kullanıcıların faturalarını görüntülediği bir endpoint'in var.

Zararsızlaştırılmış (Defanged) İstek Örneği:

GET /api/v1/billing/invoice/10045 HTTP/1.1
Host: api.example.com
Authorization: Bearer <valid_jwt_of_user_A>

Burada kullanıcı 'A', kendi faturasını (ID: 10045) görüyor. Her şey nizami. Peki, bu kullanıcı biraz meraklıysa ve Burp Suite üzerinden veya sadece tarayıcıdaki o '10045' rakamını '10046' yaparsa ne olur?

Eğer backend kodun sadece 'Bu kullanıcı login mi?' diye bakıyor ama 'Bu kullanıcı gerçekten 10046 numaralı faturanın sahibi mi?' diye sormuyorsa, geçmiş olsun. Artık rakip şirketin faturasını, komşunun harcamalarını veya tüm sistemin finansal verisini izliyorsun demektir.

Neden Hala Bu Hataları Yapıyoruz?

Dostum, dürüst olalım; işler hızlı ilerliyor. Sprint yetişecek, feature çıkacak, product manager tepende. Yazılımcı arkadaşım genelde veritabanı sorgusunu şöyle yazar:

# Kötü Uygulama (Pseudo-code)
def get_invoice(invoice_id):
    # Kullanıcı login mi kontrolü burada yapılıyor (Middleware)
    invoice = db.query("SELECT * FROM invoices WHERE id = ?", invoice_id)
    return invoice

Buradaki sorun, invoice_id'nin birincil anahtar (primary key) olması ve sorgunun kullanıcı aidiyetini sorgulamamasıdır. Red Team olarak biz buna 'Horizontal Privilege Escalation' diyoruz. Yani aynı yetki seviyesindeki kullanıcıların birbirinin verisine sızması.

Saldırganın Gözünden: O Rakamlar Neden Bu Kadar Düzenli?

Biz bir sisteme sızmaya çalışırken ilk baktığımız şeylerden biri ID yapılarıdır. Eğer ID'ler 1, 2, 3... diye artıyorsa (Incremental IDs), bu bizim için açık büfe kahvaltı demektir.

Bir script yazıp (lütfen bunu evde deneme veya sadece kendi 127.0.0.1 ortamında test et) binlerce isteği saniyeler içinde gönderebiliriz:

# Defanged/Zararsızlaştırılmış saldırı simülasyonu
for id in {1000..2000}; do 
  curl -H "Authorization: Bearer [TOKEN]" "https://api.example.com/v1/user/profile/$id" >> leaks.txt
done

Eğer sistemin hız sınırlaması (rate limiting) yoksa ve BOLA'ya açıksa, 10 dakika sonra elimde binlerce kullanıcının kişisel verisi olur.

Peki, Nasıl Savunacağız Bu Kaleyi?

'Tamam Sedat, anlattın felaketi, peki ne yapalım?' dediğini duyar gibiyim. Çözüm aslında basit ama uygulama disiplini gerektiriyor.

  1. Aidiyet Kontrolü (Ownership Check): Veritabanı sorgusunu asla sadece ID üzerinden yapma. Her zaman o kaynağın isteği yapan kullanıcıya ait olup olmadığını sorgu seviyesinde doğrula.

Doğru Uygulama (Pseudo-code):

def get_invoice(user_id, invoice_id):
    # Sorgu hem invoice_id hem de user_id içermeli
    invoice = db.query("SELECT * FROM invoices WHERE id = ? AND owner_id = ?", (invoice_id, user_id))
    if not invoice:
        return abort(404) # 403 yerine 404 dönmek, kaynağın varlığını bile gizler
    return invoice
  1. UUID (Universally Unique Identifier) Kullanımı: Artan (incremental) ID'ler yerine 550e8400-e29b-41d4-a716-446655440000 gibi tahmin edilmesi imkansız UUID'ler kullan. Bu, saldırganın 'bir sonraki ID ne olabilir?' tahminini imkansız hale getirir. Ama dikkat! UUID tek başına bir güvenlik önlemi değildir, sadece işi zorlaştırır. Aidiyet kontrolü hala şart.

  2. Büyük Veri Sızıntılarını Önlemek İçin Rate Limiting: Bir kullanıcı neden bir saniyede 500 farklı faturaya bakmak istesin ki? API gateway seviyesinde veya uygulama seviyesinde limitler koy.

  3. Güvenlik Testlerini Otomatize Et: Manuel test bir yere kadar. CI/CD süreçlerine API güvenliğini tarayan araçlar ekle. Ama en önemlisi, 'Business Logic' dediğimiz mantık hatalarını yakalamak için Unit Test yazarken 'başka birinin verisine erişmeye çalışınca ne oluyor?' senaryosunu da test et.

Gerçek Hayattan Bir Not

Bir keresinde büyük bir e-ticaret platformunda (ismini vermeyelim, testCompany-2 diyelim), adres silme endpoint'inde bir BOLA bulmuştuk. İşin kötüsü, sadece address_id parametresini değiştirerek platformdaki herhangi bir kullanıcının kayıtlı adresini silebiliyorduk. Düşünsene, biri bir script çalıştırıyor ve tüm Türkiye'deki kayıtlı teslimat adresleri bir gecede uçuyor. Kaosu hayal edebiliyor musun? İşte bu yüzden bu 'küçük' mantık hataları, en karmaşık SQL injection saldırılarından daha yıkıcı olabiliyor.

Senin İçin Bir Ödev (Defensive Geek Out)

Bugün projene geri döndüğünde, en çok kullanılan 3 endpoint'ini aç ve kendine şu soruyu sor: 'Ben bu isteği gönderen kullanıcının, bu veriye dokunmaya hakkı olduğunu kodun neresinde garanti ediyorum?' Cevabın sadece 'Middleware'deki JWT kontrolü' ise, bugün senin için biraz mesai vakti gelmiş demektir.

Unutma, siber güvenlikte en büyük açıklar genelde en karmaşık yerlerde değil, 'buradan bir şey olmaz' dediğimiz o en basit mantık hatalarında gizlidir.

Kendine iyi bak, koduna mukayyet ol. Bir sonraki sızıntı analizinde veya Red Team operasyonunda görüşmek üzere!

İlgili yazılar