Nesne özelliği düzeyi yetki ve kütle atama
- Kütle atama, API'nin istekteki her alanı olduğu gibi kaydetmesidir; aşırı veri ifşası ise yanıtta gereğinden fazla alan döndürmesidir.
- Bir kullanıcının kendi rolünü nasıl yükselttiğini istek ve yanıt üstünden izleyecek, açığı sınamanın en ucuz yolunu göreceksin.
- İzin listesi, sabit yanıt şeması ve JSON Schema doğrulamasıyla iki yönü birlikte kapatacaksın.
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.
Altay Kargo'nun müşteri profili ekranında üç alan vardır: ad, e-posta ve telefon. Kullanıcı birini değiştirip kaydeder, uygulama API'ye bir JSON gövdesi gönderir. Sunucudaki profil nesnesi ise çok daha fazla alan taşır: rol, kayıt tarihi, sadakat puanı, iç notlar. Arayüz üç alan gösteriyor diye API'nin de yalnız üç alanla çalıştığını varsaymak yaygın bir hatadır.
API bir nesneyi hem okur hem yazar. Okurken hangi alanların dışarı çıkacağını, yazarken hangi alanların içeri gireceğini kendisi seçmezse bu seçimi istemciye bırakmış olur. İstemci ise saldırganın elindedir.
Bir madde, iki yön
OWASP API Security Top 10 2023'ün üçüncü maddesi nesne özelliği düzeyi yetki hatasıdır (Broken Object Property Level Authorization). Madde iki eski riski birleştirir. 2019 listesinde aşırı veri ifşası (Excessive Data Exposure) ve kütle atama (Mass Assignment) ayrı maddelerdi.
2023'te ikisinin aynı kök nedenden doğduğu kabul edildi: API alan düzeyinde yetki denetimi yapmıyor. Nesne düzeyindeki soru doğru yanıtlanmış olabilir, kayıt gerçekten kullanıcıya aittir. Yine de o kaydın her alanı kullanıcıya açık değildir.
Kullanıcı kendi profilindeki adı değiştirebilir, ama aynı profildeki rolü değiştiremez. Kendi siparişini görebilir, ama siparişin iç maliyet alanını göremez. MITRE bu iki yönü ayrı zayıflıklar olarak izler: yazma yönü CWE-915, okuma yönü CWE-213 başlığındadır.
Yazma yönündeki hata çoğu zaman çerçevenin sunduğu kolaylıktan doğar. Pek çok çerçeve gelen JSON'u alır, alan adlarını eşleştirir ve değerleri doğrudan model nesnesine bağlar. Geliştirici tek satırla bütün nesneyi günceller.
Rails ve Node.js dünyası buna kütle atama der. Spring MVC ve .NET MVC dünyasında adı otomatik bağlamadır (autobinding), PHP dünyasında nesne enjeksiyonu. Ad değişir, davranış aynıdır: istemci modelde var olan her alanı gönderebilir.
Saldırgan rol, bakiye, onayliMi ya da indirimOrani gibi alanları tahmin eder. Çoğu zaman tahmine de gerek kalmaz, çünkü aynı nesnenin okuma yanıtı bu alanların adlarını zaten gösterir. Okuma yönündeki küçük bir sızıntı, yazma yönündeki açığın haritasını çıkarır.
Okuma yönü tersine çalışır. Sunucu nesnenin bütün alanlarını yanıt olarak döner ve süzmeyi arayüze bırakır. Arayüz yalnız adı ve soyadı gösterir, ama yanıt gövdesinde parola özeti, iç not ya da tam kimlik numarası da durur.
Kullanıcı ham API yanıtını okursa bu alanları görür; bunun için tarayıcının geliştirici araçları yeter. Hata genellikle bir kestirmeden doğar: veritabanı nesnesi doğrudan JSON'a çevrilir. Tabloya sonradan eklenen her sütun da habersizce yanıta katılır.
İki yön aynı kök nedene dayanır. Hangi alanın içeri gireceğine ya da dışarı çıkacağına API karar vermez. Arayüz katmanı güvenlik sınırı değildir, yalnız bir görünüm tercihidir.
Etkinlik 1 / 6
Bu güncelleme ucunda kütle atamaya kapı açan satırı bul.
Çözümlü örnek: rolünü kendisi yükselten kullanıcı
GET /api/kullanicilar/profil HTTP/1.1
Host: api.altaykargo.example
Authorization: Bearer <4410 numaralı müşterinin token'ı>
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 4410,
"ad": "Ayşe",
"eposta": "[email protected]",
"telefon": "05xx xxx xx xx",
"rol": "musteri",
"sadakatPuani": 120,
"parolaOzeti": "$2b$12$Qk3...",
"icNot": "iade talebi çok, dikkatli ol"
}Yanıtı satır satır oku. İlk dört alan ekranda görünen bilgilerdir. rol ve sadakatPuani kullanıcının görmesinde sakınca olmayan, ama değiştirmemesi gereken alanlardır.
parolaOzeti ve icNot ise hiç dışarı çıkmamalıdır. Parola özeti çevrimdışı kırma denemesine malzeme olur.
İç not, destek ekibinin müşteri hakkındaki yorumunu açığa çıkarır ve kişisel veri mevzuatı açısından da sorun yaratır. Bu yanıt saldırgana bir şey daha verir: modelin alan adlarını. Artık rol diye bir alan olduğunu bilir. Bir sonraki adımda bu alanı yazma isteğine ekler.
PATCH /api/kullanicilar/profil HTTP/1.1
Host: api.altaykargo.example
Authorization: Bearer <4410 numaralı müşterinin token'ı>
Content-Type: application/json
{ "ad": "Ayşe", "rol": "yonetici" }
HTTP/1.1 200 OK
{ "id": 4410, "ad": "Ayşe", "rol": "yonetici", ... }İkinci istek gövdeye tek bir alan ekler. Sunucu gövdeyi olduğu gibi güncellemeye geçirdiği için rol alanı da veritabanına yazılır. Yanıtta yeni rol görünür ve kullanıcının bir sonraki token'ı yönetici yetkisi taşır. Saldırgan bunun için özel bir araç kullanmamıştır, isteğe bir satır eklemiştir.
Gerçek olaylarda aynı desen fiyat ve onay alanlarında da görülür. OWASP'ın örneklerinden birinde bir konaklama platformunda ev sahibi, rezervasyon onay isteğine toplam ücret alanını ekleyip tutarı değiştirir. Bir başkasında kullanıcı, video açıklamasını güncellerken engelli videoyu yeniden açan bir alan gönderir. Üç örnekte de nesne kullanıcıya aittir; sorun alan düzeyindedir.
Etkinlik 2 / 6
Aşağıdaki bulguları API3'ün yazma ya da okuma yönüne yerleştir.
Ö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/4 seçenek yerleşti.
Savunma: izin listesi ve sabit yanıt şeması
const PROFIL_YAZMA = ["ad", "eposta", "telefon"];
const PROFIL_OKUMA = { id: true, ad: true, eposta: true, telefon: true };
app.patch("/api/kullanicilar/profil", oturumGerekli, async (req, res) => {
const data = {};
for (const alan of PROFIL_YAZMA) {
if (alan in req.body) data[alan] = req.body[alan];
}
const k = await db.kullanici.update({
where: { id: req.oturum.kullaniciId },
data,
select: PROFIL_OKUMA,
});
res.json(k);
});Kodu satır satır oku. İlk satır kullanıcının değiştirebileceği üç alanı sayar. İkinci satır yanıtta dönecek alanları sabitler. Döngü gövdede yalnız listedeki alanları arar ve onları yeni bir nesneye kopyalar.
Gövdede rol ya da parolaOzeti varsa döngü onlara hiç bakmaz. Güncelleme sorgusu bu temiz nesneyle çalışır. select seçeneği de veritabanından yalnız dört alanı çeker.
Yanıt şeması böylece kodun içinde sabitlenir. Tabloya yarın yeni bir sütun eklense, ne yazma listesine ne okuma listesine kendiliğinden girer. Yeni alan varsayılan olarak kapalı başlar ve biri bilerek açmadıkça kapalı kalır.
Bu yaklaşıma izin listesi denir. Tersi yasak listesidir: rol ve bakiye gibi tehlikeli alanları tek tek sayıp reddetmek. Yasak listesi daha zayıftır, çünkü yeni eklenen hassas bir alan listeye girmeyi kaçırırsa açık kalır.
OWASP'ın kütle atama rehberi bir yol daha önerir: veri aktarım nesnesi (DTO). DTO, yalnız kullanıcının düzenleyebileceği alanları taşıyan ayrı bir sınıftır.
İstek gövdesi doğrudan veritabanı modeline değil, bu dar nesneye bağlanır. Okuma yönünde de aynı fikir çalışır. Yanıt için ayrı bir nesne ya da serileştirici tanımlanır ve yalnız gereken alanlar ona kopyalanır. Böylece model ile dış dünya arasına bilinçli bir sınır çizilir.
Okuma yönünde alan seçimi role göre de değişebilir. Destek personeli müşterinin iç notunu görmelidir, müşterinin kendisi görmemelidir.
Yönetici paneli kayıt tarihini ve son giriş zamanını gösterir, mobil uygulama göstermez. Bunu tek bir yanıt şemasıyla çözmeye çalışmak çoğu zaman en geniş şemanın herkese dönmesiyle biter. Daha sağlam yol, her rol için ayrı bir okuma listesi tanımlamaktır.
Sunucu isteği yapanın rolüne bakar ve o role ait listeyi seçer. Yazma yönünde de aynı ayrım geçerlidir: destek personeli teslimat adresini düzeltebilir, ama sadakat puanını değiştiremez. Alan düzeyi yetki, nesne düzeyi yetkinin bir alt katmanıdır. Aynı merkezi yerde tanımlanırsa denetlemesi de kolaylaşır.
Derinleş:Çerçevelerin kendi korumaları
Çoğu çerçeve bu soruna kendi aracını sunar. Laravel'in Eloquent modelinde $fillable dizisi toplu doldurulabilecek alanları sayar, $guarded dizisi ise korunan alanları belirtir. Rails, denetleyicide strong parameters adlı yapıyla çalışır: require ile ana anahtar istenir, permit ile izinli alanlar seçilir. Spring MVC'de @InitBinder ile işaretli bir yöntem setAllowedFields çağırarak bağlanacak alanları daraltır.
Django REST Framework'te serileştiricinin fields listesi aynı işi görür ve read_only_fields yalnız okunacak alanları ayırır. Bu araçların ortak noktası izin listesi kurmaktır. Yine de varsayılanlara güvenme.
Bir modelde $guarded boş dizi bırakılırsa bütün alanlar toplu atamaya açılır. Bir denetleyicide permit yerine bütün parametreleri kabul eden kısayol kullanılırsa koruma devre dışı kalır. Kod incelemesinde her yazma ucunda bu listenin nerede tanımlandığını ve neyi içerdiğini bul.
Etkinlik 3 / 6
Kütle atamaya karşı hangi önlemler doğrudur?
Etkinlik 4 / 6
Profil güncelleme isteğini yalnız izinli 'ad' alanını değiştirecek biçimde düzenle.
İsteği tamamla ve gönder.
Tespit: beklenmeyen alanı ekle, sonucu karşılaştır
Bu açığı yakalamanın en ucuz yolu isteğe alan eklemektir. Önce aynı nesnenin okuma yanıtını alırsın. Yanıtta görünen ama yazma isteğinde hiç gönderilmeyen alanları listelersin. Sonra bu alanları birer birer yazma isteğine eklersin.
İlk denemede geçersiz bir değer gönder, örneğin sayı bekleyen alana bir harf. Sunucu doğrulama hatası dönerse alanı işliyor demektir. Ardından anlamlı bir değerle istek gönderir, yanıtı ve kaydın son halini karşılaştırırsın.
Alan sessizce kaydedildiyse açık doğrulanmıştır. Bu test özel bir araç gerektirmez; bir web vekil aracı ya da curl yeter. Bunu yalnız kendi lab ortamında ya da yazılı iznin olan bir hedefte dene.
{
"type": "object",
"properties": {
"ad": { "type": "string", "maxLength": 60 },
"eposta": { "type": "string", "format": "email" },
"telefon": { "type": "string", "pattern": "^[0-9 +]{10,16}$" }
},
"additionalProperties": false
}Şema doğrulama aynı kontrolü kodun önüne taşır. properties üç alanı ve her birinin tipini tanımlar. additionalProperties değeri false olduğu için şemada olmayan her alan isteği geçersiz kılar.
İstek uygulama koduna ulaşmadan 400 ya da 422 gibi bir hata yanıtıyla döner. OpenAPI 3.1 belgeleri istek gövdesini JSON Schema ile tanımlar; aynı şema hem belgeye hem doğrulamaya kaynak olur. Şema koddan ayrı tutulursa zamanla kayabilir.
Testlerin şemaya karşı çalışması bu kaymayı erken yakalar. İzleme tarafı da ipucu verir. Reddedilen her ek alan denemesi kayda geçer. Aynı istemciden art arda gelen rol, bakiye, onayliMi gibi alan adları birinin keşif yaptığını gösterir.
Etkinlik 5 / 6
Bir test ekibi, sipariş güncelleme isteğine beklenmedik bir 'indirimOrani' alanı ekledi ve alan veritabanına yazıldı. Bu bulgu neyi kanıtlar?
Formlar ve gizli alanlar
Aynı desen form tabanlı web uygulamalarında da görülür. Bir sipariş formu fiyatı ya da indirim kodunu gizli (hidden) bir alanda taşıyabilir. Gizli alan ekranda görünmez, ama formun parçası olarak sunucuya gider.
Tarayıcının geliştirici araçlarıyla değeri değiştirmek birkaç saniye sürer. İsteği bir vekil araçta yakalayıp düzenlemek de o kadar kolaydır.
Sunucu bu değere güvenirse kütle atamanın form sürümü ortaya çıkar. Çözüm de aynıdır. Fiyat, indirim ve rol gibi değerler istemciden alınmaz; sunucuda hesaplanır ya da sunucudaki kayıttan okunur. İstemciden gelen alan yalnız izin listesindeyse işlenir.
Sık gözden kaçan bir durum da iç içe nesnelerdir. Bazı ORM'ler tek bir güncelleme isteğiyle ilişkili kayıtları da yazabilir. Gövde, profil alanlarının yanında { "hesap": { "rol": "yonetici" } } gibi iç içe bir nesne taşırsa, üst düzeydeki izin listesi bunu görmeyebilir. Dizi alanları da benzer bir risk taşır; bir sipariş isteğindeki ürün satırlarına fiyat alanı eklenebilir.
İzin listesi bu yüzden yalnız üst düzey alanları değil, iç içe her nesnenin alanlarını da kapsamalıdır. JSON Schema'da additionalProperties kuralını her iç nesneye ayrıca yazman gerekir. Kural yalnız tanımlandığı nesneye uygulanır, alt nesnelere kendiliğinden geçmez.
Etkinlik 6 / 6
Doğru mu, yanlış mı?
Bir alanı HTML formunda gizli (hidden) tip olarak işaretlemek, kullanıcının o alanı değiştirmesini engeller.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.