Bir satır kod, binlerce kullanıcının verisini tehlikeye atabilir. Daha önce farkında bile olmadan birçoğumuz bu hatayı yaptı. 😬

İlk ciddi projem olan Sermify'ı yayına aldığımda her şey yolundaydı en azından öyle sanıyordum. Kullanıcılar kayıt olabiliyor ve veri girişi yapabiliyordu. Harika hissettiriyordu ta ki bir arkadaşım "Şunu bir dene" deyip bana garip bir URL gönderene kadar. Tıklayınca tarayıcım uyarı verdi. Kendi uygulamamın beni hack'lediğini o an anladım.

XSS (Cross-Site Scripting), web güvenliğinin en yaygın açıklarından biri. OWASP'ın birkaç yılda bir güncellediği Top 10 listesinde (2013, 2017, 2021) XSS her zaman yer aldı. 2021 güncellemesinde ayrı bir madde olmaktan çıkıp A03: Injection kategorisi altına alındı ama listeden çıkmadı, aksine daha geniş bir tehdit ailesinin parçası olarak tanındı. Çünkü görünmez kodu yazarken her şey normal görünür, ama saldırgan çoktan içeri girmiştir. ⚔️

Bu yazıda XSS'in ne olduğunu, kaç türünün bulunduğunu ve JavaScript projelerinizde bunu nasıl önleyeceğinizi anlatıyorum. 🔐

None

XSS Nedir? 🛡️

XSS, saldırganın bir web uygulamasına kötü niyetli bir script enjekte etmesidir. Bu script, kurbanın tarayıcısında çalışır sanki uygulamanın kendi koduymuş gibi.

Saldırgan şunları yapabilir:

  • Oturum çerezlerini çalabilir
  • Kullanıcı adına işlem gerçekleştirebilir
  • Sayfanın görünümünü değiştirebilir
  • Kullanıcıları zararlı sitelere yönlendirebilir

Kısacası: bir kullanıcı o sayfayı açtığında, saldırganın kodu da açılmış olur.

XSS'in 3 Türü

1. Stored XSS (Kalıcı) 🗡️

Saldırganın kodu veritabanına kaydedilir ve her sayfa yüklenişinde çalışır. Yorum alanları, profil biyografileri, mesaj kutuları bunların hepsi potansiyel hedef.

// Tehlikeli — kullanıcı girdisi doğrudan DOM'a yazılıyor
function renderComment(comment) {
  document.getElementById("comment-box").innerHTML = comment;
  // Eğer comment şunsa: <script>stealCookies()</script>
  // Sayfayı açan herkes bu kodu çalıştırır!
}
// Güvenli — textContent kullanılıyor
function renderComment(comment) {
  document.getElementById("comment-box").textContent = comment;
  // Script etiketleri metin olarak gösterilir, çalıştırılmaz ✓
}

💡 Pratik kural: Kullanıcıdan gelen veriyi asla innerHTML ile DOM'a yazmayın.

2. Reflected XSS (Yansıtılmış) ⚔️

Kötü niyetli kod URL parametresi olarak gönderilir, sunucu onu HTML response içine gömer ve tarayıcı sayfayı açınca script çalışır. Phishing saldırılarında çok kullanılır.

// SUNUCU TARAFI (Node.js/Express)

// ❌ Tehlikeli — kullanıcı girdisi doğrudan HTML response'a yazılıyor
app.get("/search", (req, res) => {
  const query = req.query.q;
  res.send(`<p>Aranan: ${query}</p>`);
  // URL: /search?q=<script>stealCookies()</script>
  // Sunucu bu scripti HTML'e gömer, tarayıcı çalıştırır!
});

// ✅ Güvenli — encode edilerek yazılıyor
const escapeHtml = (str) =>
  str
    .replace(/&/g, "&")
    .replace(/</g, "<")
    .replace(/>/g, ">")
    .replace(/"/g, """)
    .replace(/'/g, "&#039;");

app.get("/search", (req, res) => {
  const query = escapeHtml(req.query.q);
  res.send(`<p>Aranan: ${query}</p>`); // ✓
});

💡 Pratik kural: Reflected XSS'in kritik farkı, zararlı kodun sunucudan dönen HTTP response içinde gelmesidir. Bu yüzden önlem de sunucu tarafında alınmalıdır client-side encode tek başına yetmez.

3. DOM-Based XSS 💣

Sunucu hiç devreye girmez, saldırı tamamen client-side JavaScript içinde gerçekleşir. Bu yüzden sunucu tarafı güvenlik önlemleri onu durduramaz.

// ❌ Tehlikeli — hash değeri doğrudan DOM'a yazılıyor
const hash = window.location.hash.substring(1);
document.getElementById("section").innerHTML = decodeURIComponent(hash);
// URL: #<img src=x onerror=stealData()>
// Sunucu bu isteği hiç görmez, saldırı tamamen tarayıcıda gerçekleşir!

// ✅ Güvenli — hash değeri metin olarak işleniyor
const hash = window.location.hash.substring(1);
document.getElementById("section").textContent = decodeURIComponent(hash); // ✓

💡 Pratik kural: location.hash, document.referrer, window.name gibi tarayıcı API'larından gelen veriler de güvenilmezdir. Sunucu tarafı validasyon bunları yakalayamaz.

Önleme Stratejileri 🛡️

innerHTML Yerine Güvenli Alternatifleri Kullanın:

// ❌ Tehlikeli
element.innerHTML = userInput;

// ✅ Güvenli alternatifler
element.textContent = userInput;          // Metin içeriği için
element.setAttribute("value", userInput); // Attribute için
element.insertAdjacentText("beforeend", userInput); // Metin eklemek için

Content Security Policy (CSP) Ekleyin

CSP güçlü bir savunma katmanıdır ancak doğru yapılandırılmazsa etkisiz kalır:

✅ Güçlü CSP

Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com

❌ Zayıf — 'unsafe-inline' tüm inline scriptlere izin verir, XSS'i durduramaz

Content-Security-Policy: script-src 'self' 'unsafe-inline'

❌ Zayıf — 'unsafe-eval' eval() kullanımına izin verir, bypass kapısı açar

Content-Security-Policy: script-src 'self' 'unsafe-eval'

Ya da HTML <meta> tag ile:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

⚠️ Önemli: CSP'yi son savunma hattı olarak düşünün, tek güvenlik önlemi olarak değil. Whitelist çok geniş tutulursa kolayca bypass edilebilir.

DOMPurify Kütüphanesini Kullanın 📚

Gerçekten HTML render etmeniz gerekiyorsa mesela bir rich-text editörü için DOMPurify kullanın:

import DOMPurify from "dompurify";

// ❌ Tehlikeli — ham input doğrudan DOM'a yazılıyor
element.innerHTML = userInput;

// ✅ Güvenli — önce temizle, sonra yaz
const cleanHTML = DOMPurify.sanitize(userInput);
element.innerHTML = cleanHTML; // ✓

Çerezleri HttpOnly ile Koruyun 🛡️

XSS saldırısının en büyük hedefi oturum çerezleridir. `HttpOnly` flag ile JavaScript'in bu çerezlere erişmesini engelleyin:

Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax

React, Vue, Angular Kullananlar İçin 💡

Modern frameworkler varsayılan olarak XSS koruması sağlar ama dikkat edilmesi gereken istisnalar var:

// React — ❌ Tehlikeli
function UserProfile({ bio }) {
  return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}

// React — ✅ Güvenli (framework otomatik escape ediyor)
function UserProfile({ bio }) {
  return <div>{bio}</div>;
}

// React — ✅ HTML render etmek zorundaysanız DOMPurify ile kullanın
function UserProfile({ bio }) {
  return <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(bio) }} />;
}

dangerouslySetInnerHTML (React), v-html (Vue), [innerHTML] (Angular) — bunların hepsi XSS kapısı açar. Kullanmak zorundaysanız, DOMPurify ile temizleyerek kullanın.

Sonuç ⬇️

XSS, hafife alındığında gerçekten yıkıcı sonuçlar doğurabilir. Ama temel kurallara uyulduğunda önlemesi de o kadar zor değil:

  • Kullanıcı girdisini asla doğrudan DOM'a yazmayın
  • HTML render etmek zorundaysanız DOMPurify kullanın
  • CSP header'ı eklemeyi ihmal etmeyin
  • Framework'ünüzün tehlikeli özelliklerini bilinçli kullanın

Bu hataları daha önce yapmak normal, önemli olan artık bilmek ve yazmaya devam etmek.

XSS ile başka hangi deneyimleriniz oldu? Yorum olarak paylaşın 👇

Sırada Ne Var? 🤔

Serinin bir sonraki yazısında JavaScript ile ilgili farklı bir konuya değineceğim. Yeni yazılardan haberdar olmak ve gelişmeleri kaçırmamak için takipte kalın, hoşçakalın.

Buraya kadar okuduysanız küçük bir ricam var: bu yazı işinize yaradıysa ve "Oh be, anladım sonunda" dedirttiyse, sol aşağıda yer alan alkış (clap) butonuna basılı tutarak (evet, 50'ye kadar yolu var 🤓) destek olabilirsiniz. Bu sayede yazı daha fazla yazılımcı dostumuza ulaşabiliriz. Şimdiden çok teşekkür ediyorum. 😄😊🧡

None

Frontend Developer | Computer Engineer | Serhat İsmail Zunluoğlu