İçeriğe geç
Educora
İleri24 dk27 / 27

Güvenlik ve iyi uygulamalar

Kullanıcıları ve kodu koru: XSS ve CSRF, girdi doğrulama, sırları tarayıcıdan uzak tutmak, ESLint ve Prettier ve TypeScript'e adım adım geçiş.

Kendini test et
Bu derste öğreneceklerin
  • XSS ve CSRF'yi açıklamak ve onları textContent, kaçış, CSP, SameSite çerezleri ve CSRF token'larıyla önlemek
  • Girdiyi sunucuda izin listeleriyle doğrulamak ve sırları istemci kodundan ve Git'ten uzak tutmak
  • ESLint ve Prettier'ı kurmak ve bir projeyi kademeli olarak TypeScript'e taşımak

Sitende bir yorum alanı var. Biri yorum olarak <img src=x onerror="stealCookies()"> yazıyor. Kodun yorumları innerHTML ile ekliyorsa bu betik her ziyaretçinin tarayıcısında, onun oturumuyla çalışır. En kritik web risklerinin listesi olan OWASP Top 10'da enjeksiyon (injection) saldırıları yer alır ve XSS de bunlardandır. Güvenlik sona eklenen bir özellik değil, günlük alışkanlıklar bütünüdür.

XSS: siteler arası betik çalıştırma

XSS, güvenilmeyen veri sayfaya HTML olarak eklendiğinde ve saldırganın betiği senin sitenin adına çalıştığında olur: çerezleri çalar, kullanıcı adına işlem yapar. Savunma: kullanıcı metnini textContent ile yaz; HTML gerçekten gerekiyorsa DOMPurify gibi bir kütüphaneyle temizle; React {value} değerini kendisi kaçışlar (dangerouslySetInnerHTML hariç); Content-Security-Policy başlığı ise betiklerin nereden yüklenebileceğini sınırlar.

JavaScript
const comment = '<img src="x" onerror="stealCookies()">';

// dangerous: the text is parsed as HTML and onerror runs
list.innerHTML += `<li>${comment}</li>`;

// safe: the text stays text
const item = document.createElement('li');
item.textContent = comment;
list.append(item);
Güvenli sürümde kullanıcı ekranda yalnızca <img ...> metnini görür ve hiçbir şey çalışmaz.

Bir HTML string'i kurmak zorundaysan (örneğin sunucuda bir şablon), özel karakterleri kaçışla: < işareti &lt;, & ise &amp; olur ve tarayıcı onları etiket değil metin olarak gösterir.

JavaScript
function escapeHtml(text) {
  const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
  return String(text).replace(/[&<>"']/g, (char) => map[char]);
}

console.log(escapeHtml('<img src=x onerror="stealCookies()">'));
console.log(escapeHtml('Tom & Jerry say "hi"'));
▸ Beklenen çıktı
&lt;img src=x onerror=&quot;stealCookies()&quot;&gt;
Tom &amp; Jerry say &quot;hi&quot;

CSRF: siteler arası istek sahteciliği

Kullanıcı bankasının sitesine giriş yapmış ve oturum çerezi tarayıcıda. Sonra başka bir site açıyor ve o sayfa bankanın /transfer adresine gizli bir form gönderiyor. Tarayıcı çerezi isteğe otomatik olarak ekler, banka da işlemi kullanıcının kendisinin yaptığını sanır. Savunma: SameSite nitelikli çerezler (Chrome bu niteliği olmayan çerezleri Lax sayar), formda gizli, rastgele bir CSRF token'ı (saldırganın sitesi onu okuyamaz), Origin başlığını kontrol etmek ve veriyi asla GET isteğiyle değiştirmemek.

Text
Set-Cookie: session=8f3a2c91; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
NitelikNeye karşı korur
HttpOnlyJavaScript çerezi okuyamaz; XSS oturumu çalamaz
Secureçerez yalnızca HTTPS üzerinden gönderilir
SameSite=Laxbaşka sitelerden gelen POST isteklerine çerez eklenmez; CSRF çok zorlaşır
Max-Ageoturumun ömrünü sınırlar

Girdiyi doğrula, sırları koru

İstemciye asla güvenme: tarayıcıdan gelen her şey (formlar, URL'ler, başlıklar, JSON) sahtelenebilir; saldırgan arayüzünü atlayıp istekleri doğrudan API'ye gönderir. Bu yüzden girdiyi sunucuda doğrula: tip, uzunluk, biçim ve izin verilen değerler (yasak listesi değil, izin listesi). Tarayıcıdaki doğrulama yalnızca kullanıcının rahatlığı içindir. SQL sorgularını string birleştirerek kurma; parametreli sorgular kullan. Büyük projelerde şema kütüphaneleri (örneğin zod) yardımcı olur.

JavaScript
function validateOrder(input) {
  const errors = [];
  const allowedSizes = ['S', 'M', 'L'];

  if (typeof input.email !== 'string' || !/^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(input.email)) {
    errors.push('email: invalid format');
  }
  if (!Number.isInteger(input.quantity) || input.quantity < 1 || input.quantity > 10) {
    errors.push('quantity: must be an integer 1-10');
  }
  if (!allowedSizes.includes(input.size)) {
    errors.push(`size: must be one of ${allowedSizes.join(', ')}`);
  }
  return errors.length ? { ok: false, errors } : { ok: true };
}

console.log(validateOrder({ email: 'leyla@mail.az', quantity: 2, size: 'M' }));
console.log(validateOrder({ email: 'not-an-email', quantity: '100', size: 'XXL' }));
▸ Beklenen çıktı
{ ok: true }
{ ok: false, errors: [ 'email: invalid format', 'quantity: must be an integer 1-10', 'size: must be one of S, M, L' ] }
'100' bir string'dir; bu yüzden Number.isInteger onu reddeder, tip de kontrol edilmelidir. Tüm hataları bir kerede döndürmek kullanıcının formu hızla düzeltmesine yardım eder.

Sırlar (API anahtarları, parolalar, token'lar) tarayıcı koduna girdikleri anda herkese açık olur; her kullanıcı onları DevTools'ta görebilir. Ücretli API'yi senin sunucun çağırmalı, tarayıcı ise yalnızca senin sunucunla konuşmalı. Sırları bir .env dosyasında tut ve dosyayı .gitignore'a ekle. Vite'ta VITE_ önekli değişkenler istemci koduna gömülür; yani herkese açıktır.

Kod kalitesi: ESLint, Prettier, TypeScript

ESLint, kodu çalıştırmadan hataları ve kötü kalıpları bulur: kullanılmayan değişkenler, ==, bildirilmemiş adlar. Prettier kodu otomatik biçimlendirir; böylece ekip üslup hakkında tartışmaz. İkisini de editörde kaydederken ve CI'da çalıştır. Bir projeye ESLint eklemek için npm init @eslint/config@latest, Prettier için npm install -D prettier yeterlidir.

JavaScript
// eslint.config.js
import js from '@eslint/js';
import globals from 'globals';

export default [
  js.configs.recommended,
  {
    languageOptions: { globals: globals.browser },
    rules: {
      eqeqeq: 'error',
      'no-unused-vars': 'warn',
      'no-console': ['warn', { allow: ['warn', 'error'] }],
    },
  },
];
ESLint'in modern “flat config” dosyası: önerilen kurallar ve üç kendi kuralımız. npx eslint . tüm projeyi kontrol eder.
JavaScript
const user={name:"Aysel",age:15,city:'Baku'}
function greet(u){return "Hi, "+u.name}
if(user.age>=14){console.log(greet(user))}
Biçimlendirilmemiş kod. npx prettier --write --single-quote . çalıştırıldıktan sonra aşağıdaki gibi olur.
JavaScript
const user = { name: 'Aysel', age: 15, city: 'Baku' };
function greet(u) {
  return 'Hi, ' + u.name;
}
if (user.age >= 14) {
  console.log(greet(user));
}

TypeScript, JavaScript'e statik tipler ekler: hatalar kod çalışmadan önce editörde görünür. Büyük bir projeyi bir günde dönüştürmen gerekmez; geçiş kademeli yapılır:

  1. 1
    tsconfig.json ekle

    allowJs ve checkJs seçenekleriyle; TypeScript mevcut JS dosyalarını da kontrol etmeye başlar.

  2. 2
    JSDoc tipleri yaz

    /** @param {number} price */ gibi yorumlar dosyanın adını değiştirmeden tip ekler.

  3. 3
    Dosyaları tek tek .ts'ye çevir

    Az bağımlılığı olan en basit modüllerle başla.

  4. 4
    strict'i aç

    Son olarak "strict": true en çok hatayı yakalar; örneğin null olabilecek değerleri.

TypeScript
// shop.ts
interface Product {
  id: number;
  title: string;
  price: number;
}

function total(products: Product[]): number {
  return products.reduce((sum, p) => sum + p.price, 0);
}

total([{ id: 1, title: 'Pen', price: '2' }]);
total([{ id: 2, title: 'Notebook' }]);
Beklenen çıktı
shop.ts(12,31): error TS2322: Type 'string' is not assignable to type 'number'.
shop.ts(13,8): error TS2741: Property 'price' is missing in type '{ id: number; title: string; }' but required in type 'Product'.
npx tsc --noEmit çalıştırıldığında: iki hata kod hiç çalıştırılmadan bulundu. JavaScript'te '2' sessizce string birleştirmesine, eksik price ise NaN'a yol açardı.
Alıştırma

escapeHtml(text) fonksiyonunu yaz: &, <, >, " ve ' karakterlerini sırasıyla &amp;, &lt;, &gt;, &quot; ve &#39; ile değiştirsin. Tek bir replace ve bir değer haritası yeterli.

Alıştırma · JavaScript
function escapeHtml(text) {
  // replace & < > " ' with HTML entities
}

const comments = ['<b>Hello</b>', 'Tom & Jerry', '<img src=x onerror="steal()">'];
for (const comment of comments) {
  console.log(escapeHtml(comment));
}
▸ Beklenen çıktı
&lt;b&gt;Hello&lt;/b&gt;
Tom &amp; Jerry
&lt;img src=x onerror=&quot;steal()&quot;&gt;
Alıştırma

Açık yönlendirmeyi önle: isSafeRedirect(target) yalnızca iki durumda true döndürsün: ardından ikinci bir / ya da \ gelmeyen tek bir / ile başlayan göreli bir yol ya da ana makine adı tam olarak app.example.com olan bir https:// adresi.

Alıştırma · JavaScript
function isSafeRedirect(target) {
  // 1) relative path: /^\/(?![\/\\])/
  // 2) otherwise parse with new URL() inside try...catch
  return true;
}

const targets = [
  '/lessons/42',
  '//evil.example/login',
  '/\\evil.example',
  'https://app.example.com/profile',
  'https://app.example.com.evil.example/',
  'javascript:stealCookies()',
];
for (const target of targets) {
  console.log(`${target} -> ${isSafeRedirect(target)}`);
}
▸ Beklenen çıktı
/lessons/42 -> true
//evil.example/login -> false
/\evil.example -> false
https://app.example.com/profile -> true
https://app.example.com.evil.example/ -> false
javascript:stealCookies() -> false

Önemli noktalar

  • XSS: kullanıcı verisini asla HTML olarak ekleme; textContent, kaçış ya da temizleme kullan ve bir Content-Security-Policy ekle.
  • CSRF: çerezler otomatik gönderilir; SameSite, CSRF token'ları, Origin kontrolü ve GET ile değişiklik yapmamak korur.
  • İstemciye güvenme: sunucuda izin listeleriyle doğrula ve parametreli SQL sorguları kullan.
  • Sırlar yalnızca sunucuda ve .gitignore'daki bir .env dosyasında yaşar; sızan bir anahtarı hemen iptal et.
  • ESLint hataları bulur, Prettier biçimlendirir, TypeScript tip hatalarını çalıştırmadan yakalar; ona checkJs ile kademeli geç.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Bir kullanıcının yorumunu sayfada göstermenin hangi yolu güvenlidir?