Saldırı altında mısınız? Olay müdahalesi için bize ulaşın

[email protected]
Web güvenliği

Uygulamanızdaki her ID'yi IDOR ve BOLA için test edin

ID değişince başkalarının kayıtlarını döndüren endpoint'leri bulun, kapsamlı sorgularla düzeltin, code review'da nelere bakacağınızı öğrenin.

Diğer diller:EnglishDeutschFrançaisEspañolItalianoPolskiNederlandsEesti

İllüstrasyon: Uygulamanızdaki her ID'yi IDOR ve BOLA için test edin

Güvensiz doğrudan nesne referansı (IDOR), uygulamanızın istemciden bir tanımlayıcı alıp, bu kullanıcının ilgili kayda dokunmaya yetkisi olup olmadığını sormadan eşleşen kaydı döndürdüğü ya da değiştirdiği her yerde ortaya çıkar. API dünyasında aynı zafiyet nesne düzeyinde bozuk yetkilendirme (BOLA) olarak adlandırılır ve 2023 OWASP API Security listesinin başında yer alır. Bunu önlemek için her nesne erişiminde bir yetkilendirme kontrolü yapmak, bu kontrolü uygulamanın tamamına uygulamak ve testlerle güvence altına almak gerekir.

IDOR neyden oluşur ve nerede saklanır

OWASP IDOR cheat sheet bir IDOR'u üç parçaya ayırır: bir nesne (hesap, doküman, destek talebi, işlem), bu nesneye bir referans (ID, UUID, hesap numarası, token veya slug) ve eksik bir nesne düzeyinde kontrol. Zafiyet üçüncü parçadır.

Referans her zaman URL'deki /users/123 biçiminde olmaz. user_id gibi gizli bir form alanında, query string içinde, bir request header'ında veya bir JSON payload'ında yer alabilir. Dosya adı da olabilir: GET /documents/annual-report.pdf çalışıyorsa, geçerli başka bir doküman adını deneyin.

OWASP API Security'nin BOLA maddesi istismar edilebilirliği kolay, yaygınlığı geniş ve tespit edilebilirliği kolay olarak değerlendirir. API sunucuları genellikle istemci durumunu takip etmez, bu yüzden hangi nesneyi yükleyeceklerine karar verirken istemcinin gönderdiği ID'lere güvenirler. Maddedeki örnek senaryolar şunlardır:

  • /shops/{shopName}/revenue_data.json biçimindeki bir mağaza analitiği endpoint'i, tüm mağaza adlarını listeleyen ikinci bir endpoint ile birleştiğinde, binlerce mağazanın satış verisini basit bir script'e açar.
  • Bir VIN kabul eden ancak aracın oturum açmış kullanıcıya ait olup olmadığını hiç kontrol etmeyen bir araç API'si.
  • Aldığı her doküman ID'sini silen bir GraphQL deleteReports mutation'ı.

Son örnek bir silme işlemidir. Bu nedenle güncelleme ve silme işlemlerini, kaydı gösteren sayfa kadar titizlikle test edin.

İki hesapla test edin

Kullanıcı A ve Kullanıcı B'yi oluşturun, her birine kendi nesnelerini (dokümanlar, destek talepleri, faturalar) verin, A olarak kimlik doğrulayın ve istekleri B'nin tanımlayıcılarıyla yeniden gönderin.

  1. Farklı yetkilendirme kapsamlarına sahip iki hesap oluşturun.
  2. Her kullanıcıya ait nesneler oluşturun.
  3. Kullanıcı A olarak oturum açın ve bir nesne referansı taşıyan tüm istekleri yakalayın.
  4. Path, query, body ve header içindeki referansları Kullanıcı B'ninkilerle değiştirin.
  5. Uygulamanın erişimi her seferinde reddettiğini doğrulayın.

Okuma, oluşturma, güncelleme, silme, dışa aktarma ve yönetsel işlemlerin tümünü kapsayın. Tanımlayıcıları rastgele görünüyor diye hiçbir endpoint'i atlamayın. Cheat sheet'e göre tanımlayıcı tahmin edilebilir de olsa tahmin edilemez de olsa erişim reddedilmelidir.

Regresyon testine dönüştürün

Manuel tur çalıştıktan sonra bunu koda dökün. Aşağıda pytest ile bir taslak var. Kullanıcı A için oturum açmış bir session ve B'ye ait bir dokümanın ID'sini veren fixture'ların bulunduğunu varsayıyor. Her istek bunun dışında geçerli olmalı ve body'si düzgün biçimlendirilmiş olmalıdır. Böylece girdi doğrulamasından dönen bir 400 veya 415, testin yanlış nedenle geçmesine yol açamaz:

import os
import pytest

BASE_URL = os.environ["APP_BASE_URL"]  # from your test config
DENIED = 404  # your chosen response for foreign IDs

ENDPOINTS = [
    ("GET", "/documents/{id}", None),
    ("PUT", "/documents/{id}", {"title": "changed"}),
    ("DELETE", "/documents/{id}", None),
    ("GET", "/documents/{id}/export", None),
    ("POST", "/documents/{id}/comments", {"text": "hello"}),
]

@pytest.mark.parametrize("method,path,body", ENDPOINTS)
def test_a_cannot_touch_bs_document(method, path, body, session_a, b_document_id):
    url = BASE_URL + path.format(id=b_document_id)
    r = session_a.request(method, url, json=body)
    assert r.status_code == DENIED

403 ile 404 arasında bilinçli bir seçim yapın. Başkasına ait bir ID için dönen 403, o ID'nin var olduğunu doğrular ve saldırganın enumeration yapmasına yardımcı olur. Hangisini seçerseniz seçin, yalnızca o kodu assert edin. Endpoint listesini route tablonuzdan üretin, böylece yeni endpoint'ler otomatik olarak listeye girer. Test seti başarısız olduğunda deployment'ları engelleyin.

GraphQL için iki ek senaryo gerekir. Yetkisiz bir ID'yi ağacın her seviyesinde sorgulayarak iç içe resolver'ların üst nesnenin kontrolünü tekrarlayıp tekrarlamadığına bakın. Yukarıdaki reportKeys dizisi gibi toplu işlemlerde, kendi ID'lerinizi başkasına ait olanlarla karıştıran bir istek gönderin ve çağrının bütünüyle başarısız olduğunu ya da yabancı öğenin atlandığını doğrulayın.

Sorguyu kullanıcıya göre kapsamlandırın

Kullanıcının erişmeye çalıştığı her nesne için, framework'ünüzün sunduğu mekanizmayla yetkilendirmeyi kontrol edin. Böylece kontrol yapısal olur ve her geliştiricinin hatırlamasına bağlı kalmaz. En doğrudan yöntem, nesneleri yalnızca mevcut kullanıcının erişebildiği küme içinde aramaktır. Rails sürümü:

# vulnerable, searches all projects
@project = Project.find(params[:id])

# secure, searches projects related to the current user
@project = @current_user.projects.find(params[:id])

Cheat sheet'teki Spring Boot handler'ı findById(id) çağırır ve bu yüzden ID'yi tahmin eden ya da ele geçiren herkese herhangi bir dokümanı döndürür. Aşağıdaki kod parçası OWASP'ın değil, bizim kendi örneğimizdir. ownerId alanı üzerinde türetilmiş bir Spring Data sorgusu olduğunu varsayar. currentUser() ise güvenilir kimlik doğrulama bağlamını okuyan kendi yardımcı fonksiyonunuzu temsil eder:

@GetMapping("/documents/{id}")
public Document getDocument(@PathVariable Long id) {
    return documentRepository
        .findByIdAndOwnerId(id, currentUser().getId())
        .orElseThrow();
}

Mümkün olduğunda tanımlayıcıları dışarı açmaktan kaçının ve kullanıcıyı session'dan türetin. Çok adımlı akışlarda tanımlayıcıları session içinde tutun, böylece istemci adımlar arasında bunlarla oynayamaz.

Rastgele tanımlayıcılar yardımcı olur, ancak yalnızca derinlemesine savunmanın bir katmanı olarak. URL'lerde kullanmak üzere rastgele bir string kolonu ekleyebilir ya da primary key olarak UUID kullanabilirsiniz. ID'leri gizlemek için şifrelemeyin, bunu güvenli biçimde yapmak zordur.

Code review'da nelere bakılmalı

İstemcinin gönderdiği bir ID'yi alan her handler'ı okuyun ve izin kontrolünün nerede yapıldığını bulun. Önce sorgunun kullanıcıya kapsamlanmış bir küme üzerinde mi çalıştığına, yoksa global bir find çağırıp sonradan (hiç yapılıyorsa) kontrol mü yaptığına bakın. Ardından, istemci girdisinden bir kayda ulaşan her fonksiyonda, güncelleme, silme ve dışa aktarma dahil, kontrolün bulunduğunu doğrulayın.

Yalnızca oturumdaki kullanıcı ID'sini (örneğin JWT'den çıkarılan, JWT aracımızla inceleyebileceğiniz değeri) ID parametresiyle karşılaştıran handler'lara dikkat edin. OWASP bunun yalnızca küçük bir durum alt kümesini kapsadığını belirtir. Yetkilendirme, kullanıcı politikalarını ve hiyerarşiyi izlemelidir. Çok kiracılı (multi-tenant) bir sistemde X kiracısındaki bir admin, Y kiracısına ait bir faturayı isteyebilir. Kullanıcı kimlik doğrulamasından geçmiştir, rolü admindir ve ID geçerlidir. Dolayısıyla düz bir rol kontrolü geçebilirken nesne yanlış kiracıda durur.

Önceliklendirme yaparken iki zafiyeti birbirinden ayırın. BOLA'da kullanıcının endpoint'i çağırmasına tasarım gereği izin verilir ve ihlal, manipüle edilen ID'dir. Bir kullanıcı hiç erişmemesi gereken bir endpoint'e ulaşıyorsa bu, işlev düzeyinde bozuk yetkilendirmedir ve çözümü farklıdır.

Nereden başlamalı

  1. Tanımlayıcı kabul eden her endpoint'i, formu ve GraphQL operasyonunu, dosya adları ve gizli alanlar dahil, envantere alın. OpenAPI spec'inizden veya route tablonuzdan başlayın, ardından uygulamada gezinirken yalnızca proxy geçmişinde görünenleri ekleyin.
  2. Yukarıdaki iki kullanıcılı turu önce en hassas on nesne üzerinde, örneğin faturalar, kişisel veriler ve yönetsel işlemler için çalıştırın ve ret olmayan her yanıtı kaydedin.
  3. Global sorguları, framework'ünüzün yetkilendirme mekanizmasını kullanarak kullanıcıya kapsamlanmış sorgularla değiştirin.
  4. Parametreli kullanıcılar arası testleri pipeline'ınıza ekleyin ve bir başarısızlığın release'i engellemesini sağlayın.
  5. İnceleme sorularını pull request şablonunuza ekleyin. Testlerinizin gözden kaçırdıklarını dışarıdan kontrol ettirmek için sızma testi hizmetimizden yararlanabilirsiniz.

Nasıl yardımcı olabiliriz

Sızma testi

Ağlarınıza, uygulamalarınıza ve çalışanlarınıza gerçek bir saldırgan gibi saldırır, her açığı nasıl kapatacağınızı gösteririz.

JWT çözücü

Bir JSON Web Token’ı çözün ve yaygın güvenlik hatalarına karşı kontrol edin; token tarayıcınızdan çıkmaz.

Bize yazın
AngelForce ekibi

Bu yazıyı AngelForce ekibi hazırladı: Avrupa ve Türkiye’deki kurumların sistemlerini test eden, izleyen ve koruyan; CEH, LPT Master ve CISA sertifikalı güvenlik danışmanları.

Aşağıda listelenen herkese açık kaynaklardan yapay zekâ desteğiyle yazıldı ve yayından önce AngelForce ekibi tarafından incelendi.

Kaynaklar

  1. cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html
  2. api-security.owasp.org/editions/2023/en/0xa1-broken-object-level-authorization/

Web güvenliği hakkında yazılar

Neyi korumanız gerektiğini anlatın

Altyapınızı ve hedefinizi kısaca yazın, size e-postayla dönelim.

Bize yazın
[email protected]

AngelForce OÜ · Tallinn, Estonya