Hızlı cevap: NoSQL injection, MongoDB gibi SQL kullanmayan veritabanlarında, kullanıcı girdisinin sorgu nesnesine güvenilmeden karışması sonucu sorgunun mantığının değiştirilmesidir. Klasik örnek operatör enjeksiyonudur: giriş formunda parola yerine {"$ne": null} (null'a eşit değil) göndermek, "parolası herhangi bir şey olan kullanıcı" anlamına gelir ve kimlik doğrulamayı atlatabilir. $gt, $regex, $where gibi operatörlerle veri sızdırma ve koşul atlama da mümkündür. SQL injection'dan farkı tırnak kaçışı değil, girdinin sorgu nesnesindeki rolüdür. Çözüm girdiyi beklenen türe zorlamak (string gelmeli, nesne değil), girdiyi sorgu yapısına değil yalnızca değer olarak kullanmak, $where ve sunucu-taraflı JavaScript'i kapatmaktır.

"NoSQL kullanıyoruz, SQL injection bize işlemez" yanılgısı yaygındır ve tehlikelidir. Enjeksiyon SQL'e özgü değildir; kullanıcı girdisinin bir sorgunun yapısını değiştirebildiği her yerde vardır. MongoDB ve benzeri belge veritabanlarında bu, tırnak işaretiyle değil, sorgu operatörleriyle olur. Bu rehber NoSQL injection'ın nasıl çalıştığını, en sık görülen kimlik-doğrulama atlatma ve veri sızdırma örneklerini, SQL injection'dan farkını ve doğru savunmayı anlatır.

SQL injection'dan farkı: tırnak değil, operatör

SQL injection'da saldırgan genelde tırnak işareti ve SQL anahtar kelimeleriyle sorguyu kırar. NoSQL'de ise sorgular çoğu zaman JSON benzeri nesnelerdir ve saldırı, girdinin bir değer yerine bir operatör nesnesi olarak yorumlanmasıyla gerçekleşir.

Klasik örnek: giriş sorgusu { kullanici: girilen_ad, parola: girilen_parola } biçiminde kuruluyor. Uygulama istek gövdesini doğrudan bu nesneye koyuyorsa, saldırgan parola alanına düz metin yerine {"$ne": null} gönderebilir. Sorgu artık "parolası null'a eşit olmayan kullanıcı" anlamına gelir, yani parolası dolu olan ilk kullanıcıyla eşleşir ve kimlik doğrulama atlanır. Benzer biçimde {"$gt": ""} (boştan büyük) de tüm kayıtlarla eşleşir.

Buradaki kök neden, istek gövdesindeki bir alanın string beklenirken nesne olarak kabul edilmesidir. JSON API'lerde bu özellikle kolaydır çünkü istemci "parola": "123" yerine "parola": {"$ne": null} göndermekte serbesttir ve sunucu türü kontrol etmezse bunu olduğu gibi sorguya koyar.

En sık görülen saldırı biçimleri

Teknik Örnek Etki
$ne / $gt ile kimlik atlatma {"$ne": null} parola alanında Parola bilmeden giriş
$regex ile veri çıkarımı {"$regex": "^a"} ile alan harf harf tahmin Gizli değeri kör sızdırma
$where / JavaScript enjeksiyonu "this.gizli == x" benzeri ifade Sunucuda mantık çalıştırma, DoS
Operatör ile filtre atlatma Yetki/sahiplik filtresini $ne ile bozma Başka kullanıcının verisi
Dizi/nesne ile mantık bozma Beklenen string yerine dizi Beklenmeyen sorgu davranışı

$regex tabanlı saldırılar özellikle sinsidir: saldırgan gizli bir değeri (örneğin bir sıfırlama anahtarı) doğrudan göremese bile, karakterleri tek tek deneyerek (önce ^a, sonra ^ab ...) yanıtın değişmesinden değeri çıkarabilir. Bu, SQL'deki kör enjeksiyonun (blind injection) NoSQL karşılığıdır. $where operatörü ise sunucuda rastgele JavaScript çalıştırmaya kadar gidebildiği için en tehlikeli olanıdır ve genelde tamamen kapatılmalıdır.

Nasıl tespit edilir

Tespit, girdinin tür sınırının test edilmesiyle başlar. Pentester bir alana string yerine operatör nesnesi ({"$ne": null}) veya dizi göndererek davranışın değişip değişmediğine bakar: giriş beklenmedik biçimde başarılı oluyor, farklı bir hata dönüyor veya yanıt süresi/uzunluğu değişiyorsa açık işaret verir. $regex ile karakter-karakter çıkarım, kör durumda değerin sızdırılabildiğini kanıtlar.

Bu testler meşru isteği yakalayıp gövdeyi düzenleyerek yapılır; Burp Suite rehberimizde anlattığımız akış burada da temeldir. Enjeksiyon sınıfının genel mantığını SQL injection yazımızda ayrıntılı ele aldık; NoSQL injection aynı kök nedenin (girdi ile komut/sorgu yapısının karışması) farklı bir tezahürüdür.

Doğru savunma: tür doğrulama ve değer ayrımı

NoSQL injection'ın çözümü, girdinin sorgu yapısını değil yalnızca değeri belirlemesini garantilemektir:

  • Girdi türünü zorlayın. Parola, kullanıcı adı gibi alanlar string olmalıdır. İstek gövdesinde bu alan bir nesne veya dizi olarak geldiyse reddedin. Bir doğrulama şeması (örneğin katı tip tanımlı) bunu otomatik yapar ve {"$ne": null} gibi girdileri daha sorguya ulaşmadan eler.
  • Girdiyi değer olarak kullanın. Sorgu nesnesini kullanıcı girdisiyle doğrudan birleştirmeyin; alanı açıkça string'e dönüştürüp öyle kullanın, böylece operatör nesnesi anlamını yitirir.
  • $where ve sunucu-taraflı JavaScript'i kapatın. Bu özellikler çoğu uygulamada gereksizdir ve en tehlikeli saldırı yüzeyini oluşturur; veritabanı seviyesinde devre dışı bırakın.
  • En az yetki. Uygulamanın bağlandığı veritabanı kullanıcısı yalnızca ihtiyaç duyduğu koleksiyon ve işlemlere erişsin; bir enjeksiyon başarılı olsa bile etkisi sınırlı kalsın.
  • Girdi doğrulamayı sunucuda yapın. İstemci-taraflı kontrol yeterli değildir; saldırgan isteği doğrudan API'ye gönderir.

Bu yaklaşım genel girdi-doğrulama disipliniyle örtüşür ve OWASP Top 10 web uygulama güvenliği çerçevesindeki enjeksiyon maddesinin NoSQL'e uygulanmış halidir.

KAOS ve DSET yaklaşımı

DSET olarak enjeksiyon testlerinde veritabanı türüne göre ayrım yapmayız; SQL kullanmayan bir sistem gördüğümüzde NoSQL operatör ve JavaScript enjeksiyonunu özellikle test ederiz, çünkü "NoSQL güvenli" yanılgısı bu açıkları sahada sık bırakır. Testlerimizde kimlik-doğrulama ve filtre uç noktalarına operatör nesneleri enjekte eder, $regex ile kör çıkarımı dener ve açığı yalnızca tespit etmez, atlatılan kontrolü veya sızdırılan veriyi kanıtlayarak doğrularız. Geliştirdiğimiz KAOS aracı, string beklenen alanlarda nesne/operatör girdisini kabul eden uç noktaları ve açık $where kullanımını işaretler, ama son kararı kanıta bağlar. Uygulamanızı NoSQL injection'a karşı test ettirmek için Ankara Hacettepe Teknokent'teki ekibimizle DSET ile iletişime geçin.

Sık sorulan sorular

MongoDB kullanıyorum, parametreli sorgu yok mu? NoSQL'de "parametreli sorgu"nun SQL'deki tam karşılığı farklı çalışır; temel ilke girdinin sorgu yapısını değil değeri belirlemesidir. Pratikte bu, alanları beklenen türe (string) zorlamak ve kullanıcı girdisini operatör olarak yorumlanamayacak biçimde kullanmakla sağlanır. Katı girdi doğrulama (nesne/dizi gelirse reddet) en etkili tek önlemdir.

$ne ile giriş atlatma gerçekten çalışır mı? Sunucu istek gövdesindeki parola alanını tür kontrolü yapmadan sorguya koyuyorsa evet. {"$ne": null} sorguyu "parolası dolu olan kullanıcı" anlamına çevirir ve ilk eşleşen hesapla giriş yapılabilir. Çözüm basittir: parola alanı string değilse isteği hiç işlemeyin. Bu tek doğrulama bu saldırı sınıfının çoğunu kapatır.

NoSQL injection sadece MongoDB'de mi olur? Hayır. Kullanıcı girdisinin sorgu veya komut yapısına karışabildiği her NoSQL sisteminde (belge, anahtar-değer, grafik veritabanları) benzer riskler vardır; yalnızca operatör ve sözdizimi değişir. İlke her yerde aynıdır: girdiyi değer olarak kullanın, yapı olarak değil, ve sunucu-taraflı kod çalıştıran özellikleri kapatın.

Kaynaklar