SQL Injection Test Etme

Web tasarım, Web Programlama ve script dilleri konuları buraya
Cevapla
Kullanıcı avatarı
velociraptor
Yottabyte4
Yottabyte4
Mesajlar: 51171
Kayıt: 14 Mar 2006, 02:33
cinsiyet: Erkek
Teşekkür etti: 12456 kez
Teşekkür edildi: 9498 kez

SQL Injection Test Etme

Mesaj gönderen velociraptor »

Bir SQL Injectionii saldırısı istemci tarafından uygulamaya SQL sorgusu ekleme veya "enjekte" etme ile gerçeklesir. Basarılı bir SQL Injection saldırısı veritabanından önemli bilgilerin okunabilmesi ile, degistirilebilmesi ile (Insert/Update/Delete), veritabanında yönetici islemleri yapılabilmesiyle (Veritabanı sunucusunun kapatılması gibi) veya bazı durumlar isletim sistemi komutları çalıstırılabilmesi ile sonuçlanabilir.

Problemin Tanımı
SQL Injection saldırıları asagıdaki gibi 3 sınıfa ayrılabilir:

* Inband: Veri SQL kodunu enjekte etmekte kullanılan aynı kanal ile elde edilir. Alınan verilerin direk olarak uygulama web sayfasında gösterildigi bu tip kullanım saldırının en düz kullanımıdır.
* Out-of-band: Veri farklı bir kanal kullanılarak elde edilir (ör: Sorgunun sonuçlarının test edene eposta ile gönderilmesi)
* Sonuç Çıkarma: Gerçekte veri transferi olmaz, fakat test eden belirli bazı istekler gönderip veritabanı sunucusunun davranısını gözlemleyerek bilgiyi olusturabilir.

Saldırı sınıfından bagımsız olarak, bir SQL Injection saldırısı, saldırganın yazılım olarak dogru bir SQL sorgusu hazırlamasını gerektirir. Eger uygulama dogru olmayan bir sorgu sebebiyle hata mesajı dönerse, orjinal sorgunun mantıgını çözmek ve enjeksiyonu dogru olarak nasıl yapılacagını ögrenmek kolaydır. Bununla beraber, eger uygulama hata detaylarını gizliyorsa, test edenin orjinal sorgu mantıgını tersten çözmesi gerekir. Buna da "Kör SQL Enjeksiyoni" (Blind SQL Injection) adı verilir.

Kara Kutu Testleri ve Örnekler
SQL Enjeksiyon Tespiti
Test isleminin ilk adımı uygulamamızın verilere erismek için ne zaman veritabanı sunucusuna baglandıgını anlamaktır. Bir uygulamanın veritabanı ile konusma ihtiyacı duydugu durumlara tipik bir örnek olarak:

* Kimlik Dogrulama Formları: Kimlik dogrulama bir web formu ile gerçeklestirildiginde, genelde girilen kullanıcı bilgilerinin, tüm kullanıcı adı ve sifrelerin tutuldugu bir veritabanında karsılastırılması gerekir.
* Arama motorları: Kullanıcı tarafından girilen deger bir SQL sorgusunda kullanılarak veritabanındaki tüm kayıtlarda aranır.
* E-Ticaret Siteleri: Ürünler ve özellikleri (fiyat, açıklama, mevcudiyet,..) veritabanında tutulur.

Test islemini gerçeklestirenin POST isteklerindeki gizli alanlarda dahil SQL sorgusunda kullanılabilecek tüm giris alanlarının listesini yapması ve tek tek test etmesi, sorguya müdahele edip hatalar yaratması gerekir. İlk testlerden biri tek tırnak (') veya noktalı vigrül (;) kullanımıdır. Tekli tırnak SQL sorgusunda string sonlandırıcı olarak kullanılır ve uygulama tarafından filtrelenmiyorsa geçersiz bir sorgu ile sonuçlanabilir. Noktalı virgül ise bir SQL sorgusunun sonlandırılmasında kullanılır ve eger filtrelenmemisse bu da bir hata yaratacaktır. Dogru filtrelenmemis bir alan (örnekte MS SQL sunucusu) asagıdaki mesajı verdirebilir:
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the
character string ''.
/target/target.aspi, line 113

Ayrıca kendisinden sonra gelenlerin bir yorum oldugunu ve göz ardı edilip çalıstırılmaması gerektigini belirten -- karakterleri ve AND OR gibi SQL kelimeleri de sorguyu degistirmede kullanılır. Basit fakat etkili bir teknik basitçe degerinin rakam olması beklenen bir yere harf girmektir. Asagıdaki gibi bir hata mesajı ile karsılasılır:
Microsoft OLE DB Provider for ODBC Drivers error '80040e07'
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the
varchar value 'test' to a column of data type int.
/target/target.asp, line 113

Örnekteki gibi tam hata mesajları test edene fazla bilgi saglayarak basarılı bir enjeksiyon yapabilmelerine yardımcı olur. Bununla beraber, bazen uygulamalar hiçbir detay vermeden sadece '500 Server Error' veya özel hazırlanmıs bir hata sayfası döndürebilir. Bu gibi durumlarda kör enjeksiyon teknikleri kullanmamız gerekir. Her durumda, tüm alanların ayrı ayrı test edilmesi çok önemlidir. Hangi parametrelerin etkilenip hangilerinin etkilenmedigin dogru olarak bulmak için parametrelerden sadece biri degistirilip digerleri sabit tutulmalı.

Standart SQL Enjeksiyon Testi
Asagıdaki SQL Sorgusunu ele alalım:
SELECT * FROM Uyeler WHERE Kullanici='$kullanici' AND Sifre='$sifre'

Buna benzer sorgular genelde web uygulamasının kullanıcı kimligini dogrulaması gerektiginde kullanılır. Eger sorgu bir deger dönerse bu isim ve sifreye sahip bir kullanıcı olduguna isaret eder ve kullanıcı sisteme alınır, aksi durumda erisimi engellenir. Giris alanlarının degerleri genelde kullanıcı tarafından web formuna girilir. Asagıdaki parametreleri girdigimizi varsayalım:
$kullanici = 1' or '1' = '1
$sifre = 1' or '1' = '1

Sorgu asagıdaki gibi olacaktır
http://www.example.com/index.phpi?kulla ... '%20=%20'1

Kısa bir analiz sonrası sorgunun bir deger (veya birden fazla veri) döndügünü görürüz çünkü sart her zaman dogrudur (OR 1=1). Bu durumda sistem kullanıcı adı ve sifreyi bilmeden kullanıcının kimlik dogrulamasını yapmıs olur.

Bir diger sorgu tipi olarak:
SELECT * FROM Uyeler WHERE ((Kullanici='$kullanici') AND (Sifre=MD5('$sifre')))

Bu durumda, iki problemimiz var, biri parantez kullanımı ve digeri de MD5 hash fonksiyonunun kullanımı. İlk olarak parantez problemini çözelim. Bu basitçe dogru sorguyu tutturana kadar parantez kapama eklemektir. İkinci problemi çözmek için ikinci sartı geçersiz hale getirmeye çalısabiliriz. Sorgumuza yorum baslangıcı karakterini ekleyerek arkasından gelenlerin göz ardı edilmesini saglayabiliriz. Her veritabanı yönetim sisteminin kendi yorum sembolleri vardır. Fakat çogu veritabanında kullanılan genel bir sembol /* karakterleridir. Oracle da bu sembol -- karakterleridir. Bunu göz önüne alırsak kullanacagımız kullanıcı adı ve sifre degerleri:
$kullanici = 1' or '1' = '1'))/*
$sifre = falan

Bu sayede sorgu asagıdaki gibi olur:
SELECT * FROM Uyeler WHERE ((Kullanici='1' or '1' = '1'))/*') AND (Password=MD5('$password')))

URL istegi asagıdaki gibi olur:
http://www.example.com/index.php?kullan ... ifre=falan

Bazen kimlik dogrulama kodu döndürülen tuple'ın (veritabanında bir kayıt olusturan veri seti) tam olarak 1'e esit oldugunu kontrol eder. Önceki örneklerde bu durum zorluk çıkarırdı (veritabanında her kullanıcı için bir deger). Bu problemi asmak için, döndürülen tuple'ın 1 oldugunu gösteren bir SQL komutu eklememiz yeterlidir. (Kayıt getirildikten sonra) bunu saglamak için, "LIMIT <rakam>" komutunu kullanırız. <rakam> degeri döndürülmesini istedigimiz tuple sayısını belirtir. Buna göre önceki örnegimizdeki kullanıcı adı ve sifre alanı degerleri asagıdaki gibi olur:
$kullanici = 1' or '1' = '1')) LIMIT 1/*
$sifre = falan

Bu sekilde asagıdaki gibi bir istek yapabiliriz:
http://www.example.com/index.php?kullan ... ifre=falan

Union sorgusu SQL Enjeksiyon Testi
Yapılacak testlerden biri de UNION islemi kullanımıdır. Bu islem ile orjinal SQL sorgusuna baska bir sql sorgusu eklemek mümkün olabilmektedir. Eklenen sorgunun sonuçları orjinal sorgununkilere eklenir ve test edenin diger tablolardaki verilerin degerlerini alabilmesini saglar. Örnegimiz asagıdaki gibi olsun:
SELECT AdSoyad, Telefon, Adres FROM Uyeler WHERE Id=$id

Id degiskenine asagıdaki degeri atayacagız:
$id=1 UNION ALL SELECT krediKartiNumarasi,1,1 FROM KrediKartiTablosu

Asagıdaki sorguya sahip olmus olacagız:
SELECT AdSoyad, Telefon, Adres FROM Uyeler WHERE Id=1 UNION ALL SELECT krediKartiNumarasi,1,1 FROM KrediKartiTablosu

Bu da tüm kredi kartı kullanıcılarını orjinal sorgunun sonuçlarına ekleyecektir. ALL parametresi DISTINCT komutu kullanımını asmak için gereklidir. Kredi kartı numaralarının yanında iki farklı deger daha seçtik. Bu iki deger önemsiz, eklenmesinin sebebi her iki sorgunun da aynı sayıda parametreye sahip olması gerekliligi. Aksi takdirde yazım hatası ile karsılasılır.

Kör SQL Enjeksiyon Testi (Blind SQL Injection)
İslem sonuçlarının bilinmedigi SQL enjeksiyon tipine Kör SQL Enjeksiyon denir. Bu davranıs programcının özel bir hata sayfası hazırlamasıyla sorgu veya veritabanı yapısı hakkında hiçbir bilgi gösterilmemesi ile olusur (Bir SQL hatası döndürülmez, sadece bir HTTP 500 dönebilir).

Bu engeli asmak mümkün olabilmektedir. Metod çesitli Boolean sorgularını sunucuya çalıstırmak, cevapları gözlemlemek ve cevapların anlamını çözmekten olusur. Örnegimizde yine www.example.com sitesinde id parametresinin SQL enjeksiyondan etkilendigini varsayalım. Eger asagıdaki istegi yaparsak:
http://www.example.com/index.php?id=1'
Özel hazırlanmıs bir mesaj olan bir sayfa ile karsılasıyoruz. Sunucuda çalıstırılan sorgunun asagıdaki gibi oldugunu varsayalım:
SELECT field1, field2, field3 FROM Users WHERE Id='$Id'
Bu sorgu da önceki metodlar ile kullanılabilir. Amacımız kullanıcı adı alanı degerlerini elde etmek olsun. Testimiz karakter karakter ayrıstırarak bu alanın degerlerini edinmemizi saglayacak. Bu hemen hemen tüm veritabanlarında bulunan bazı standart fonksiyonlar ile mümkün. Örnegimizde asagıdaki pseudo-fonksiyonları kullanacagız:
SUBSTRING (yazi, baslangic, uzunluk): verilen yazının baslangıç pozisyonundan verilen uzunluk ölçüsündeki kısmını geri döndürür. Eger baslangıç degeri uzunluk degerinden fazla isefonksiyon null deger döndürür.
ASCII(karakter): Girilen karakterin ASCII degerini verir. Eger karakter 0 ise null deger döndürür.
LENGTH(yazi): Girilen yazidaki karakter sayısını verir.

Bu fonksiyonlarile ilk karakter üzerinde testlerimizi gerçeklestirecegiz ve degeri bulduktan sonra ikinciye geçecegiz. Bu tüm veri elde edilene kadar devam edecek. Test'de bir kerede sadece bir karakter seçilmesi için SUBSTRING fonksiyonu kullanılacak (tek bir karakter seçimi uzunluk parametresinin 1 olarak verilmesi ile oluyor) ve karakterin ASCII degerini elde etmek için ASCII fonksiyonu kullanılacak, böylece nümerik karsılastırma yapabilecegiz. İstenen degeri bulabilmek için karsılastırma sonuçları tüm ASCII tablosu ile yapılacak. Örnek olarak id parametresi için asagıdakini girecegiz:
$Id=1' AND ASCII(SUBSTRING(kullanici,1,1))=97 AND '1'='1
Bu da asagıdaki sorguyu olusturacak (bundan sonra buna "sonuç çıkarma sorgusu" diyecegiz):
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND ASCII(SUBSTRING(kullanici,1,1))=97 AND '1'='1'
Bu sorgu eger kullanici alaninin ilk karakterinin ASCII degeri 97 ise bir sonuç dönecektir. Eger yanlıs deger alınırsa ASCII tablo indeks degerini 97 degerini 98'e çıkarıyoruz ve istegi tekrarlıyoruz. Eger dogru deger elde edilirse SUBSTRING fonksiyonu parametrelerini degistirerek tablonun indeksini sıfırlıyoruz ve bir sonraki karaktere geçiyoruz. Burdaki olay test'in döndürdügü sonuçlarda dogru ve yanlısı ayırtedebilmek. Bunun için de yanlıs deger döndürdügüne emin oldugumuz bir parametre giriyoruz:
$Id=1' AND '1' = '2
bu da asagıdaki sorguyu yaratıyor:
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND '1' = '2'
Sunucunun döndügü cevap (HTML kod) testlerimiz için yanlıs degeri olusturacak. Bazen bu metod ise yaramaz. Sunucunun arka arkaya yapılan iki aynı web istegine iki farklı sayfa dönmesi durumunda dogru deger ile yanlıs degeri birbirinden ayıramayız. Bu özel durumlarda bazı özel filtreler kullanarak iki istek arasında degisen kodu elememiz ve bir sablon edinmemiz gerekir. Daha sonra her bir sonuç çıkarma sorgusu istegi çalıstırmada aynı fonksiyon ile relatif sablonu cevaptan çıkaracagız ve testin sonucuna karar vermek için iki sablon arasında kontrol yapacagız. Önceki testlerde bir deger elde ettigimiz için sonuç çıkarma sorgusunun ne zaman sonlandıgını bilebiliyorduk. Bu testlerdeise SUBSTRING ve LENGTH fonksiyonlarının bir karakteristigini kullanacagız. Testimiz dogru bir deger döndügünde ve ASCII kod 0'a esit oldugunda (null degeri), sona ermis demektir, veya analiz ettigimiz deger null degerini içermektedir.
id için asagıdaki degeri girecegiz:
$Id=1' AND LENGTH(kullanici)=N AND '1' = '1
Buradaki N analiz ettigimiz karakterlerin sayısı (null degeri hariç). Sorgumuz asagıdaki gibi olacaktır:
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND LENGTH(kullanici)=N AND '1' = '1'

Bu da bize dogru veya yanlıs bir deger dönecektir. Eger dogru deger alırsak, sonuç çıkarma islemimiz tamamlanmıs demektir ve parametrenin degerini elde etmisiz demektir. Eger yanlıs deger alırsak, parametre degerinde null degeri var demek ve diger bir null degeri bulana kadar bir sonrakini test etmeye devam etmemiz gerekmektedir.

Kör SQL Enjeksiyon saldırısı çok yük sayıda sorguya ihtiyaç duyar. Test edenin bu is için otomatik bir araç kullanması gerekir. Bu islemi GET istekleri ile MySQL veritabanı için deneyen araçlardan biri olan SqlDumper ekran görüntüsü asagıda görülebilir:

Stored Procedure Enjeksiyon
Soru: SQL Enjeksiyon riski nasıl yok edilir?
Cevap: Stored Procedure'ler ile.
Bu cevaba çok rastladım. Aslında stored procedure kullanımı SQL Enjeksiyonu engelleyemeyebilir. Eger düzgün islenmezse, stored procedure'ler içindeki dinamik SQL bir web sayfasındaki dinamik SQL kadar enjeksiyona açık olabilir.

Stored procedure içinde dinamik SQL kullanırken, uygulama kod enjeksiyon riskini yok etmek için kullanıcı girislerini dogru olarak filtrelemelidir. Eger bu yapılmazsa kullanıcılar stored procedure içinde çalıstırılabilecek kötü amaçlı SQL girebilirler.
Asagıdaki SQL sunucusu stored procedure'ünü ele alalım:
Create procedure kullanici_login @kullanici varchar(20), @sifre varchar(20) As
Declare @sqlstring varchar(250)
Set @sqlstring = ‘
Select 1 from uyeler
Where kullanici = ‘ + @kullanici + ‘ and sifre = ‘ + @sifre
exec(@sqlstring)
Go

Kullanıcı girisi:
falancakullanici or 1=1'
falancasifre

Yukarıdaki prosedür kullanıcı girislerini dogru olarak filtrelemektedir.

Örnekteki prosedür pek kullanıcı login islemi için kullanılmaz. Baska bir örnek olarak kullanıcının görüntülemek istedigi kolonları seçtigi bir dinamik raporlama sorgusunu ele alalım. Bu durumda da kullanıcı istedigi kodu ekleyebilir.

Asagıdaki SQL sunucusu stored procedure'ünü ele alalım:
Create procedure get_report @columnamelist varchar(7900) As
Declare @sqlstring varchar(8000)
Set @sqlstring = ‘
Select ‘ + @columnamelist + ‘ from ReportTable‘
exec(@sqlstring)
Go

Kullanıcı girisi:
1 from uyeler; update uyeler set sifre = 'olympos'; select *

Bunun sonucunda rapor çalısır ve tüm kullanıcıların sifreleri güncellenir.

Kaynak: http://www.owasp.org/index.php/Testing_ ... _Injection
Cevapla