SQL Injection Saldırısı Test Teknikleri


http://infosecurity.us/images/sql_injection.jpg 
Bir SQL Injection saldırısı istemci tarafından uygulamaya SQL sorgusu ekleme veya "enjekte" etme ile gerçekleşir. Başarılı bir SQL Injection saldırısı veritabanından önemli bilgilerin okunabilmesi ile, değiştirilebilmesi ile (Insert/Update/Delete), veritabanında yönetici işlemleri yapılabilmesiyle (Veritabanı sunucusunun kapatılması gibi) veya bazı durumlar işletim sistemi komutları çalıştırılabilmesi ile sonuçlanabilir.
Problemin Tanımı
SQL Injection saldırıları aşağı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österildiği 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ışını gözlemleyerek bilgiyi oluşturabilir.
Saldırı sınıfından bağımsız olarak, bir SQL Injection saldırısı, saldırganın yazılım olarak doğru bir SQL sorgusu hazırlamasını gerektirir. Eğer uygulama doğru olmayan bir sorgu sebebiyle hata mesajı dönerse, orjinal sorgunun mantığını çözmek ve enjeksiyonu doğru olarak nasıl yapılacağını öğrenmek kolaydır. Bununla beraber, eğer uygulama hata detaylarını gizliyorsa, test edenin orjinal sorgu mantığını tersten çözmesi gerekir. Buna da "Kör SQL Enjeksiyon" (Blind SQL Injection) adı verilir.
Kara Kutu Testleri ve Örnekler
SQL Enjeksiyon Tespiti
Test işleminin ilk adımı uygulamamızın verilere erişmek için ne zaman veritabanı sunucusuna bağlandığını anlamaktır. Bir uygulamanın veritabanı ile konuşma ihtiyacı duyduğu durumlara tipik bir örnek olarak:
  • Kimlik Doğrulama Formları: Kimlik doğrulama bir web formu ile gerçekleştirildiğinde, genelde girilen kullanıcı bilgilerinin, tüm kullanıcı adı ve şifrelerin tutulduğu bir veritabanında karşılaştırılması gerekir.
  • Arama motorları: Kullanıcı tarafından girilen değer 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 işlemini gerçekleştirenin POST isteklerindeki gizli alanlarda dahil SQL sorgusunda kullanılabilecek tüm giriş 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 eğer filtrelenmemişse bu da bir hata yaratacaktır. Doğru filtrelenmemiş bir alan (örnekte MS SQL sunucusu) aşağı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.asp, line 113
Ayrıca kendisinden sonra gelenlerin bir yorum olduğunu ve göz ardı edilip çalıştırılmaması gerektiğini belirten -- karakterleri ve AND OR gibi SQL kelimeleri de sorguyu değiştirmede kullanılır. Basit fakat etkili bir teknik basitçe değerinin rakam olması beklenen bir yere harf girmektir. Aşağıdaki gibi bir hata mesajı ile karşılaşı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 sağlayarak başarılı bir enjeksiyon yapabilmelerine yardımcı olur. Bununla beraber, bazen uygulamalar hiçbir detay vermeden sadece '500 Server Error' veya özel hazırlanmış 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 etkilenmediğin doğru olarak bulmak için parametrelerden sadece biri değiştirilip diğerleri sabit tutulmalı.
Standart SQL Enjeksiyon Testi
Aşağı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ı kimliğini doğrulaması gerektiğinde kullanılır. Eğer sorgu bir değer dönerse bu isim ve şifreye sahip bir kullanıcı olduğuna işaret eder ve kullanıcı sisteme alınır, aksi durumda erişimi engellenir. Giriş alanlarının değerleri genelde kullanıcı tarafından web formuna girilir. Aşağıdaki parametreleri girdiğimizi varsayalım:
$kullanici = 1' or '1' = '1
$sifre = 1' or '1' = '1
Sorgu aşağıdaki gibi olacaktır
http://www.example.com/index.php?kullanici=1'%20or%20'1'%20=%20'1&sifre=1'%20or%20'1'%20=%20'1
Kısa bir analiz sonrası sorgunun bir değer (veya birden fazla veri) döndüğünü görürüz çünkü şart her zaman doğrudur (OR 1=1). Bu durumda sistem kullanıcı adı ve şifreyi bilmeden kullanıcının kimlik doğrulamasını yapmış olur.
Bir diğer sorgu tipi olarak:
SELECT * FROM Uyeler WHERE ((Kullanici='$kullanici') AND (Sifre=MD5('$sifre')))
Bu durumda, iki problemimiz var, biri parantez kullanımı ve diğeri de MD5 hash fonksiyonunun kullanımı. İlk olarak parantez problemini çözelim. Bu basitçe doğru sorguyu tutturana kadar parantez kapama eklemektir. İkinci problemi çözmek için ikinci şartı geçersiz hale getirmeye çalışabiliriz. Sorgumuza yorum başlangıcı karakterini ekleyerek arkasından gelenlerin göz ardı edilmesini sağlayabiliriz. Her veritabanı yönetim sisteminin kendi yorum sembolleri vardır. Fakat çoğu veritabanında kullanılan genel bir sembol /* karakterleridir. Oracle da bu sembol -- karakterleridir. Bunu göz önüne alırsak kullanacağımız kullanıcı adı ve şifre değerleri:
$kullanici = 1' or '1' = '1'))/*
$sifre = falan
Bu sayede sorgu aşağıdaki gibi olur:
SELECT * FROM Uyeler WHERE ((Kullanici='1' or '1' = '1'))/*') AND (Password=MD5('$password')))
URL isteği aşağıdaki gibi olur:
http://www.example.com/index.php?kullanici=1'%20or%20'1'%20=%20'1'))/*&sifre=falan
Bazen kimlik doğrulama kodu döndürülen tuple'ın (veritabanında bir kayıt oluşturan veri seti) tam olarak 1'e eşit olduğunu kontrol eder. Önceki örneklerde bu durum zorluk çıkarırdı (veritabanında her kullanıcı için bir değer). Bu problemi aşmak için, döndürülen tuple'ın 1 olduğunu gösteren bir SQL komutu eklememiz yeterlidir. (Kayıt getirildikten sonra) bunu sağlamak için, "LIMIT " komutunu kullanırız. değeri döndürülmesini istediğimiz tuple sayısını belirtir. Buna göre önceki örneğimizdeki kullanıcı adı ve şifre alanı değerleri aşağıdaki gibi olur:
$kullanici = 1' or '1' = '1')) LIMIT 1/*
$sifre = falan

Bu şekilde aşağıdaki gibi bir istek yapabiliriz:
http://www.example.com/index.php?kullanici=1'%20or%20'1'%20=%20'1'))%20LIMIT%201/*&sifre=falan
Union sorgusu SQL Enjeksiyon Testi
Yapılacak testlerden biri de UNION işlemi kullanımıdır. Bu işlem ile orjinal SQL sorgusuna başka bir sql sorgusu eklemek mümkün olabilmektedir. Eklenen sorgunun sonuçları orjinal sorgununkilere eklenir ve test edenin diğer tablolardaki verilerin değerlerini alabilmesini sağlar. Örneğimiz aşağıdaki gibi olsun:
SELECT AdSoyad, Telefon, Adres FROM Uyeler WHERE Id=$id
Id değişkenine aşağıdaki değeri atayacağız:
$id=1 UNION ALL SELECT krediKartiNumarasi,1,1 FROM KrediKartiTablosu
Aşağıdaki sorguya sahip olmuş olacağı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ı aşmak için gereklidir. Kredi kartı numaralarının yanında iki farklı değer daha seçtik. Bu iki değer önemsiz, eklenmesinin sebebi her iki sorgunun da aynı sayıda parametreye sahip olması gerekliliği. Aksi takdirde yazım hatası ile karşılaşılır.
Kör SQL Enjeksiyon Testi (Blind SQL Injection)
İşlem sonuçlarının bilinmediği SQL enjeksiyon tipine Kör SQL Enjeksiyon denir. Bu davranış programcının özel bir hata sayfası hazırlamasıyla sorgu veya veritabanı yapısı hakkında hiçbir bilgi gösterilmemesi ile oluşur (Bir SQL hatası döndürülmez, sadece bir HTTP 500 dönebilir).
Bu engeli aşmak mümkün olabilmektedir. Metod çeşitli Boolean sorgularını sunucuya çalıştırmak, cevapları gözlemlemek ve cevapların anlamını çözmekten oluşur. Örneğimizde yine www.example.com sitesinde id parametresinin SQL enjeksiyondan etkilendiğini varsayalım. Eğer aşağıdaki isteği yaparsak:
http://www.example.com/index.php?id=1'
Özel hazırlanmış bir mesaj olan bir sayfa ile karşılaşıyoruz. Sunucuda çalıştırılan sorgunun aşağıdaki gibi olduğunu 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ı değerlerini elde etmek olsun. Testimiz karakter karakter ayrıştırarak bu alanın değerlerini edinmemizi sağlayacak. Bu hemen hemen tüm veritabanlarında bulunan bazı standart fonksiyonlar ile mümkün. Örneğimizde aşağıdaki pseudo-fonksiyonları kullanacağız:
SUBSTRING (yazi, baslangic, uzunluk): verilen yazının başlangıç pozisyonundan verilen uzunluk ölçüsündeki kısmını geri döndürür. Eğer başlangıç değeri uzunluk değerinden fazla isefonksiyon null değer döndürür.
ASCII(karakter): Girilen karakterin ASCII değerini verir. Eğer karakter 0 ise null değer döndürür.
LENGTH(yazi): Girilen yazidaki karakter sayısını verir.
Bu fonksiyonlarile ilk karakter üzerinde testlerimizi gerçekleştireceğiz ve değeri bulduktan sonra ikinciye geçeceğiz. 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 değerini elde etmek için ASCII fonksiyonu kullanılacak, böylece nümerik karşılaştırma yapabileceğiz. İstenen değeri bulabilmek için karşılaştırma sonuçları tüm ASCII tablosu ile yapılacak. Örnek olarak id parametresi için aşağıdakini gireceğiz:
$Id=1' AND ASCII(SUBSTRING(kullanici,1,1))=97 AND '1'='1
Bu da aşağıdaki sorguyu oluşturacak (bundan sonra buna "sonuç çıkarma sorgusu" diyeceğiz):
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND ASCII(SUBSTRING(kullanici,1,1))=97 AND '1'='1'
Bu sorgu eğer kullanici alaninin ilk karakterinin ASCII değeri 97 ise bir sonuç dönecektir. Eğer yanlış değer alınırsa ASCII tablo indeks değerini 97 değerini 98'e çıkarıyoruz ve isteği tekrarlıyoruz. Eğer doğru değer elde edilirse SUBSTRING fonksiyonu parametrelerini değiştirerek tablonun indeksini sıfırlıyoruz ve bir sonraki karaktere geçiyoruz. Burdaki olay test'in döndürdüğü sonuçlarda doğru ve yanlışı ayırtedebilmek. Bunun için de yanlış değer döndürdüğüne emin olduğumuz bir parametre giriyoruz:
$Id=1' AND '1' = '2
bu da aşağıdaki sorguyu yaratıyor:
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND '1' = '2'
Sunucunun döndüğü cevap (HTML kod) testlerimiz için yanlış değeri oluşturacak. Bazen bu metod işe yaramaz. Sunucunun arka arkaya yapılan iki aynı web isteğine iki farklı sayfa dönmesi durumunda doğru değer ile yanlış değeri birbirinden ayıramayız. Bu özel durumlarda bazı özel filtreler kullanarak iki istek arasında değişen kodu elememiz ve bir şablon edinmemiz gerekir. Daha sonra her bir sonuç çıkarma sorgusu isteği çalıştırmada aynı fonksiyon ile relatif şablonu cevaptan çıkaracağız ve testin sonucuna karar vermek için iki şablon arasında kontrol yapacağız. Önceki testlerde bir değer elde ettiğimiz için sonuç çıkarma sorgusunun ne zaman sonlandığını bilebiliyorduk. Bu testlerdeise SUBSTRING ve LENGTH fonksiyonlarının bir karakteristiğini kullanacağız. Testimiz doğru bir değer döndüğünde ve ASCII kod 0'a eşit olduğunda (null değeri), sona ermiş demektir, veya analiz ettiğimiz değer null değerini içermektedir.
id için aşağıdaki değeri gireceğiz:
$Id=1' AND LENGTH(kullanici)=N AND '1' = '1
Buradaki N analiz ettiğimiz karakterlerin sayısı (null değeri hariç). Sorgumuz aşağıdaki gibi olacaktır:
SELECT field1, field2, field3 FROM Uyeler WHERE Id='1' AND LENGTH(kullanici)=N AND '1' = '1'
Bu da bize doğru veya yanlış bir değer dönecektir. Eğer doğru değer alırsak, sonuç çıkarma işlemimiz tamamlanmış demektir ve parametrenin değerini elde etmişiz demektir. Eğer yanlış değer alırsak, parametre değerinde null değeri var demek ve diğer bir null değeri 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 iş için otomatik bir araç kullanması gerekir. Bu işlemi GET istekleri ile MySQL veritabanı için deneyen araçlardan biri olan SqlDumper ekran görüntüsü aşağı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. Eğer düzgün işlenmezse, 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ı girişlerini doğru olarak filtrelemelidir. Eğer bu yapılmazsa kullanıcılar stored procedure içinde çalıştırılabilecek kötü amaçlı SQL girebilirler.
Aşağı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ı girişi:
falancakullanici or 1=1'
falancasifre
Yukarıdaki prosedür kullanıcı girişlerini doğru olarak filtrelemektedir.
Örnekteki prosedür pek kullanıcı login işlemi için kullanılmaz. Başka bir örnek olarak kullanıcının görüntülemek istediği kolonları seçtiği bir dinamik raporlama sorgusunu ele alalım. Bu durumda da kullanıcı istediği kodu ekleyebilir.
Aşağı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ı girişi:
1 from uyeler; update uyeler set sifre = 'ayhansari'; select *
Bunun sonucunda rapor çalışır ve tüm kullanıcıların şifreleri güncellenir.
Kaynak: http://www.owasp.org/index.php/Testing_for_SQL_Injection

0 yorum:

Yorum Gönder