Badstore SQL Based Penetration Testing!

Bu rapor, Badstore 1.2.3 platformunda gerçekleştirilen SQL Injection tabanlı sızma testi sürecini özetlemektedir. Testte, kullanıcı doğrulama mekanizması ve veritabanı yapılandırmasındaki güvenlik açıkları incelenmiş, hassas verilere ve sistem dosyalarına izinsiz erişim sağlanmıştır. Çalışma, sistem güvenliğini artırma gerekliliğini vurgulamaktadır.

author image

Written by

Furkan İbiş

Published on

Dec 14

Badstore SQL Based Penetration Testing!

Badstore 1.2.3

Ufak tatlı açıklamalar

Bugün sizlere Badstore 1.2.3 cihazına yönelik gerçekleştirdiğim SQL Injection tabanlı sızma testinin raporunu sunacağım.

Badstore 1.2.3'ten bahsedecek olursak, kendisi bir online satış platformudur; ancak içerisinde birçok zafiyet barındırmaktadır.

Vulnhub tarafından sunulan harika bir platformdur. Doğrudan indirmek isterseniz, şu bağlantıyı kullanabilirsiniz: Badstore 1.2.3.

VirtualBox üzerinde kurduğum iki adet cihazım var. Birisi, tahmin edebileceğiniz gibi Badstore'nin kendisi, diğeri ise bir Kali cihazı. Saldırılarımızı bu Kali cihazından gerçekleştireceğiz. Bu senaryo doğrultusunda, iki cihazın aynı ağda bulunduğunu varsayabilirsiniz.

Sayfaya ilk girdiğimizde, zengin bir arayüze sahip olan Badstore'nin ana sayfası ile karşılaşıyoruz. 😊

Sayfa üzerinde kısa bir gezintinin ardından, ürün arama için oluşturulan metin kutusuna boş bir değerle arama yaptığımızda şu çıktıyı görüyoruz:

Burada yaşanan olayı kısaca özetlemek gerekirse, her ne kadar Badstore'nin backend tarafını görmüyor olsak da, Python ile şu kodlara benzer bir yapının çalıştığını söyleyebilirim:

user_inp = get_user_inp()
sql_query = f"SELECT itemnum, sdesc, ldesc, price FROM itemdb WHERE '{user_inp}' IN (itemnum,sdesc,ldesc)"
curr.execute(sql_query)
items_user_wanted = curr.fetch_all()
if len(items_user_wanted) > 0:
	return items_user_wanted
else:
	return ('No items matched your search criteria:', sql_query)

Örnek olarak yazdığım bu kodda önemli bir sorun şudur: Kullanıcının girdiği arama kriteri arka planda bir kontrole tabi tutulmuyor ve doğrudan SQL sorgularının içerisine aktarılıyor. Peki, bu durum ne gibi sorunlara yol açabilir?

Bu durum, uygulamanın SQL Injection saldırılarına açık hale gelmesine neden olur. Yani, saldırgan, arama kutusuna özel hazırlanmış SQL ifadeleri yazarak:

  1. Veritabanındaki verilere yetkisiz erişim sağlayabilir (örneğin, kullanıcı adları ve şifreler gibi hassas bilgileri görüntüleyebilir).
  2. Veritabanında veri manipülasyonu yapabilir (örneğin, kayıtları silebilir, güncelleyebilir veya yenilerini ekleyebilir).
  3. Sisteme daha fazla zarar verebilecek komutları çalıştırabilir (örneğin, veritabanı sunucusunun işletim sistemine erişim sağlayabilir).

Bu nedenle, kullanıcı girdileri mutlaka doğrulanmalı ve SQL sorguları için parametreli sorgular (prepared statements) veya ORM (Object-Relational Mapping) araçları kullanılmalıdır.

Aksi takdirde, yaptığınız uygulama kötü niyetli kişiler tarafından raporumuzun ilerleyen aşamalarında gerçekleştirdiğimiz işlemlere maruz kalabilir. Bu da sisteminizin veya uygulamanızın ciddi şekilde zarar görmesine yol açabilir. 😊

Sızma Testi

Ufak açıklamaların ardından sıra bilgi edinme kısmına geldi. Bir sistem üzerinde ne kadar fazla bilgi sahibi olursak, o kadar güçlü bir saldırı gerçekleştirebileceğimiz kaçınılmaz bir gerçektir. Bu yazıda saldırı türümüz SQL Injection olduğundan, hedef sistemin kullandığı SQL dili ve versiyonu, ayrıca hangi veritabanı yönetim sistemi (DBMS) kullanıldığı gibi bilgiler bizim için kritik öneme sahiptir.

Hedefimiz neleri kullanıyor?

Sistemimizde hangi veritabanının çalıştığını öğrenmek için, nmap aracını kullanıyorum. Bunun için aşağıdaki komutu çalıştırıyorum:

sudo nmap -sS -p- 192.168.1.116

Hedef sistemimizin MySQL kullandığını bir kenara not alıyoruz. Bu bilgi, sonraki adımlarda sorgularımızı özelleştirmek için oldukça önemli. 😊

Bu bilgiler, SQL Injection saldırılarını özelleştirmemize ve daha etkili hale getirmemize olanak tanır.

Sayfa içerisinde seach kısmına hiçbir şey vermeden yaptığım bir aratmada aşağıdaki görselle karşılaşıyorum.

1'='1' # komutu, SQL Injection için en temel denemelerden biri olarak bu yazıdaki ilk komutumuz olacak. Yaptığım denemelerde, searchquery içerisine bu komutu yazdığımda, sayfanın tüm ürünlerini gönderdiğini keşfettim. Bunun nedeni, sorgunun aşağıdaki hale gelmesi:

SELECT itemnum, sdesc, ldesc, price 
FROM itemdb 
WHERE '1'='1' # ' IN (itemnum, sdesc, ldesc)

Burada # işareti, SQL dilinde yorum satırı olarak kullanıldığından, sorgunun son kısmı dikkate alınmaz ve şu basitleştirilmiş hale gelir:

SELECT itemnum, sdesc, ldesc, price 
FROM itemdb 
WHERE '1'='1'

Bu durumda '1'='1' ifadesi her zaman doğru olduğundan, veritabanı tüm ürünleri döndürür. Bu da bize sayfanın barındırdığı tüm ürünlerin listelenmesini sağlar.

Önemli bir detay: Yazdığım komutları doğrudan URL parametresine yazmadan önce, online bir URL encoder aracı kullanarak encode ettim. Çünkü, yanlış bir URL formatı yazıldığında, sayfa otomatik olarak sizi ana sayfaya yönlendiriyor. Bu, test işleminin düzgün çalışması için kritik bir adımdır. 😊

Amacımız şu anlık sayfadaki ürünlerle ilgilenmek değil, ancak sayfanın ilk sorgusundan aşağıdaki gibi bir yapı olduğunu anlıyoruz:

SELECT itemnum, sdesc, ldesc, price 
FROM itemdb 
WHERE '' IN (itemnum,sdesc,ldesc)Ufak açıklamaların ardından sıra bilgi edinme kısmına geldi. Bir sistem üzerinde ne kadar fazla bilgi sahibi olursak, o kadar güçlü bir saldırı gerçekleştirebileceğimiz kaçınılmaz bir gerçektir. Bu yazıda saldırı türümüz SQL Injection olduğundan, hedef sistemin kullandığı **SQL dili** ve **versiyonu**, ayrıca hangi **veritabanı yönetim sistemi (DBMS)** kullanıldığı gibi bilgiler bizim için kritik öneme sahiptir.

sorgusunda sorgunun bize 4 adet kolon döndüğünü görüyoruz. Sıradaki hedefim bir UNION sorgusu olduğundan bu ufak bilgi bizim için önemli çünkü bildiğiniz üzere UNION 2 adet sorguyu birleştiren yegane bir komuttur ancak birleştirilen sorgulardan dönecek olan verilerin eşit kolon sayısına sahip olmaları gerekir.

Sorgumu bu mantığa göre güncelliyorum ve aşağıdaki komutu searchquery'ye veriyorum:

1'='0' UNION SELECT 1,1,1,1 #

Yaptıklarımı açıklayacak olursam:

  1. 1='1' ifadesini 1='0' olarak değiştirmemin sebebi, ürünlerle ilgilenmek yerine kendi yazdığım SQL sorgularının döndürdüğü değeri görmek istememdir. Bu şekilde, ana sorgunun sonuçları boş olur ve UNION sorgusunun döndürdüğü değerler görünür hale gelir.
  2. SELECT 1,1,1,1 ifadesi ise ufak bir deneme diyebiliriz. 😊 Bu, UNION ile birleşen sorgunun çalışıp çalışmadığını test etmek için kullanılıyor.
  3. # işareti, kalan kısmı yorumlayarak sorgunun geri kalanını devre dışı bırakıyor.

Hedef sistemimiz, hangi MySQL versiyonunu kullanıyor ve tablolarını hangi veritabanında saklıyor?

Bu sorguyu çalıştırarak, aşağıdaki gibi bir çıktı elde ediyorum:

Şimdi istediğimiz bilgileri elde etmek için deneyeceğimiz sorgulara geldi ilk başta hedef sistemin versiyonunu ve db ismini almak istiyoruz bunun için sorguya şunları ekliyoruz

1'='0' UNION SELECT VERSION(),1,1,1 UNION SELECT DATABASE(),1,1,1#

Information Schema'yı kullanmayı denemek.

Veritabanımızın adı badstoredb ve versiyonu 4.1.7-standard. Ancak amacımız, ürünlerden ziyade kullanıcılarla ilgili bilgileri görmek olduğundan, bu bilgilerin saklandığı tabloyu bulmamız gerekiyor. Bunun için information_schema tablosunu kullanacağım ve sorgumu şu şekilde değiştiriyorum:

1'='0' UNION SELECT (SELECT TABLE_NAME 
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'badstoredb' LIMIT 1),1,1,1 #

Ancak ufak bir sorunumuz var:

Neden Information Schema'yı kullanamıyoruz?

Yaptığımız sorgularda MySQL 4.1.7-standard versiyonunu kullandığımızı tespit etmiştik. Derin araştırmalarım sonucunda sorunun kaynağını buldum: Information Schema, MySQL 5.0.2 versiyonunda eklendi. Yani, kısaca şu an Information Schema'mız mevcut değil. 😔

Diğer tabloların isimlerini nasıl öğrenebiliriz?

Peki, kullanıcıların tutulduğu veritabanı tablosunu nasıl öğreneceğiz? İnsanlığın sahip olduğu en mütevazı yeteneklerden biriyle: 'doğaçlama'!

İlk tespit ettiğimiz tablonun itemdb olduğunu biliyorduk. Bunun gibi, çeşitli tablo isimlerini denemeye başladım: user, userdb, users, usersdb gibi. Tahmin ettiğimiz gibi, tablomuzun adı userdb. Bunu, aşağıdaki sorgunun hata vermemesinden anlayabiliyoruz:

1'='0' UNION SELECT 1,1,1,1 FROM userdb #

UserDB tablosunun kolonları neler ve bunları nasıl öğrenebiliriz?

Peki, tablo ismini öğrendik, ancak bu tablonun kolonlarının isimleri neler? Bunu öğrenmek için yine information_schema'ya başvurabilirdik tabii ki, ancak yukarıdaki problemi tekrar yaşamaktan başka bir şey kazanamazdık. Bu nedenle farklı bir yaklaşım düşünmeliydim.

Denediğim bazı yöntemler şunlardı, ancak yalnızca syntax hataları aldım. Muhtemelen bu MySQL versiyonunda bu yöntemler de desteklenmiyor:

SHOW COLUMNS.field FROM userdb
DESCRIBE userdb

Bu durumda, kolon isimlerini öğrenmek için farklı bir strateji izlememiz gerekiyor.

Çok sayıda deneme yaptım, ancak çoğu sonuç vermedi. Yine de sonunda şu işlemle sorunu çözmeyi başardım: Login sayfasında gezinirken, Full Name alanının input adının fullname olduğunu fark ettim. Bu gözlemle, bir deneme atışı yapmaya karar verdim:

1'='0' UNION SELECT fullname,1,1,1 from userdb #

Bu sayede, ilk kolonumuzun adını bulmuş olduk. Daha sonra aynı yöntemi email, passwd ve pwdhint isimleriyle denediğimde başarılı sonuçlar elde ettim. Aşağıdaki sorguyu çalıştırdığımda ise kullanıcı bilgilerini içeren veriler karşıma çıktı. Kısaca sorgu şu şekildeydi:

1'='0' UNION SELECT fullname,email,passwd,pwdhint from userdb #

Bu sorgu, tabloyu ve kolonları doğrulamakla kalmadı, aynı zamanda verileri de elde etmemi sağladı. 🎯

Elde ettiğimiz kullanıcı tablosuyla neler yapabiliriz ve bu bilgileri kullanarak nasıl giriş yapabiliriz?

Kullanıcılar arasında admin kullanıcısı dikkatimi çekti. Şimdi bu noktada çeşitli yöntemlerimiz var:

  1. Hashlenmiş Şifreyi Çözmek:
    Bunu hash kırma araçları veya online hash kırıcılar kullanarak çözmeyi deneyebiliriz.

  2. Brute Force veya Wordlist Tabanlı Hit:
    Şifreyi brute force veya yaygın şifre listelerini (wordlist) kullanarak çözebiliriz. Özellikle admin kullanıcılarının zayıf şifreler kullandığı senaryolarda etkili olabilir.

Ancak, bu yöntemlerin ötesinde daha güzel bir yaklaşım var: Eğer SQL Injection aracılığıyla doğrudan şifre kontrol mekanizmasını atlayabilirsek, hash çözmeye gerek kalmadan sisteme erişim sağlayabiliriz! 😊 Bu, hedef sistemin login mantığını manipüle ederek admin oturumunu doğrudan ele geçirmeyi içerir.

Login ekranını geçmek.

Yukarıda anlattığım yapıya benzer bir yapı çalışıyor diye tahmin ediyorum. Ancak kısaca yeniden açıklamak gerekirse, kullanıcının girdiği email ve parola, backend tarafında aşağıdaki gibi bir yapıya yerleştiriliyor olmalı:

username, password = get_user_inp()
sql_query = f"SELECT * FROM userdb WHERE email='{username}' AND passwd='{password}'"
curr.execute(sql_query)
user_exists = curr.fetch_all()
if len(user_exists) > 0:
	return items_user_wanted

Yine aynı mantık aslında; eğer kullanıcının girdiği inputlar kontrol edilmezse, sayfa yine manipüle edilebilir. Admin kullanıcısının email'inin admin olduğunu biliyorduk. Login sayfasında Email Address inputuna şu bilgiyi verirsek:

admin' #
SELECT * FROM userdb WHERE email='admin' #' AND ''=passwd

Kısaca temiz haliyle sorgu şu şekilde olur:

SELECT * FROM userdb WHERE email='admin'

Yukarıda görüldüğü gibi, SQL Injection yöntemiyle admin hesabına başarılı bir şekilde giriş yapmayı başardık. 🎯 Bu işlem, kullanıcı inputlarının kontrol edilmemesi ve sorgunun manipüle edilebilmesi nedeniyle mümkün oldu.

Hedef sistem dosyalarını elde etmek.

Peki, daha ne kadar ileri gidebiliriz? MySQL'in LOAD_FILE isimli metodunu kullanarak hedef sistem içerisinde bilgi edinmek mümkün. Bu yöntem, veritabanı sunucusundaki dosyaları okuma imkanı sağlar. Örneğin, aşağıdaki komutla /etc/passwd dosyasından sistemle ilgili bilgileri elde edebiliriz:

1' = '0' UNION SELECT 1,1,1,LOAD_FILE('/etc/passwd') #

Bu komutla, sistem üzerinde 2 adet kullanıcının bulunduğunu görebiliyoruz: root ve nobody kullanıcıları.

Hataların kullanıcıya doğru bir şekilde sunulması neden bu kadar önemlidir?

Badstore üzerinde daha önce hata aldığımızda karşımıza çıkan badstore.cgi dosyasının içeriğini merak ediyorum. Bu dosyada neler olduğunu öğrenmek için aşağıdaki sorguyu çalıştırıyorum:

1' = '0' UNION SELECT 1,1,1,LOAD_FILE('/usr/local/apache/cgi-bin/badstore.cgi') #

Burada, elde ettiğimiz badstore.cgi dosyasının içeriğini inceliyor ve işimize yarayabilecek kısımları araştırıyorum. Bu tür dosyalar genellikle kritik bilgiler içerebilir. Tam da istediğim gibi bir bilgi buluyorum! 🎯

Hedef sistemin MySQL veritabanına bağlanmak.

Hedef sistemimizin MySQL kullanıcı adı root ve parolası secret olarak tespit edildi. Bu bilgi, sisteme doğrudan erişim sağlamak için kritik bir adım! Ayrıca, backupların /usr/local/apache/htdocs/backup/ klasörü altında bulunduğunu tespit ettik. Bu klasör, daha fazla bilgi edinmek ve potansiyel olarak hassas verilere erişmek için incelenebilir. Ancak, şu an hedef sisteme bağlanmayı tercih ediyorum.

mysql -u root -p -h 192.168.1.116 --skip_ssl

Hedef cihazın MySQL'ine başarılı bir şekilde giriş yapmış olduk. 🎉 Artık, veritabanını keşfetmek ve potansiyel olarak kritik bilgilere erişmek için doğrudan sorgular çalıştırabiliriz.

Veritabanını küçük bir kopyasını oluşturmak. 😊

Tüm bu veritabanı tablolarını tek tek incelemek yerine, artık veritabanının tüm verilerini kendi sistemime bir kopyasını oluşturmak ve bu sızma testini burada sonlandırmanın yeterli olacağını düşünüyorum.

mysqldump -h 192.168.1.116 -u root -p badstoredb > badstore_database.sql

Ve işlemimizi burada sonlandırıyorum. Kısaca, bu süreçte yaptığımız işlemleri özetleyecek olursak:

Gerçekleştirdiğimiz İşlemler:

  1. Bilgilerini Bilmeden Kullanıcı Olarak Oturum Açmak: SQL Injection yoluyla şifre doğrulamasını atlayarak admin dahil çeşitli kullanıcılarla oturum açtık.
  2. Sunucudaki Dosyaları Okumak: MySQL'in LOAD_FILE fonksiyonunu kullanarak sistemdeki kritik dosyaların içeriklerini görüntüledik.
  3. Veritabanını Yedeklemek: Veritabanının tam bir kopyasını alarak kendi sistemimize aktardık.
  4. Hassas Bilgileri Görüntülemek: Kullanıcı bilgileri, tablolar ve diğer önemli verileri elde ettik.

Hala Yapılabilecek Diğer İşlemler:

  1. Sistem Komutlarını Yürütmek: Eğer MySQL kullanıcısının sistem seviyesinde yeterli yetkileri varsa, sys_exec() veya başka yöntemlerle komut yürütmek mümkün olabilir.
  2. Veritabanını DROP Etmek: Veritabanını tamamen silmek.
  3. Yeni Kayıt Eklemek veya Olan Kayıtları Güncellemek: Veritabanına kötü amaçlı veya manipüle edilmiş veriler eklemek.