Yazılım

PHP Uygulamalarında Güvenlik: SQL Injection ve XSS'e Karşı Pratik Önlemler

Hazırlanmış ifadeler, bağlama uygun çıktı kaçışı, Content Security Policy ve güvenli hata yönetimi: PHP projelerinde en yaygın iki saldırıya karşı uygulanabilir önlemler.

Enjeksiyon saldırıları yıllardır OWASP Top 10 listesinin üst sıralarında yer alıyor. İyi haber şu: SQL Injection ve XSS'in büyük bölümü, baştan doğru kurulmuş birkaç alışkanlıkla tamamen önlenebilir. Bu yazıda PHP projelerinde uyguladığım temel önlemleri kod örnekleriyle özetliyorum.

SQL Injection: kullanıcı verisi asla sorgu metnine girmez

Sorun, kullanıcıdan gelen değerin SQL metnine string birleştirmeyle eklenmesidir:

// YANLIŞ: e-posta alanına ' OR '1'='1 yazılırsa sorgu değişir
$sql = "SELECT * FROM users WHERE email = '" . $_POST['email'] . "'";

Çözüm, hazırlanmış ifadeler (prepared statements) kullanmaktır. Sorgu ve veri veritabanına ayrı ayrı gider; veri hiçbir zaman komut olarak yorumlanmaz:

$pdo = new PDO($dsn, $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();

Parametre olamayan yerler: beyaz liste

Tablo adı, sütun adı veya sıralama yönü parametre olarak bağlanamaz. Bu değerleri kullanıcıdan alıyorsanız, yalnızca önceden tanımlı seçeneklerden birini kabul edin:

$allowed = ['created_at', 'price', 'title'];
$column = in_array($_GET['sort'] ?? '', $allowed, true) ? $_GET['sort'] : 'created_at';
$stmt = $pdo->query("SELECT * FROM listings ORDER BY $column DESC");

Ek olarak, uygulamanın veritabanı kullanıcısına yalnızca ihtiyaç duyduğu yetkileri verin; web uygulamasının tablo silme yetkisine sahip olması gerekmez.

XSS: veriyi çıktıya yazarken kaçışlayın

XSS, kullanıcının girdiği bir içeriğin sayfada HTML veya JavaScript olarak çalışmasıdır. Temel kural: veriyi saklarken değil, ekrana yazarken ve yazdığınız bağlama uygun biçimde kaçışlayın.

function e(mixed $value): string
{
    return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>
<h2><?= e($listing['title']) ?></h2>
<a href="/ilan/<?= e(rawurlencode($listing['slug'])) ?>">Detay</a>
  • ENT_QUOTES tek ve çift tırnağı da kaçışlar; öznitelik (attribute) içinde kullanım için şarttır.
  • Kullanıcıdan gelen bir URL'yi bağlantıya yazmadan önce yalnızca http ve https şemalarına izin verin; javascript: ile başlayan adresler kaçışlansa bile çalışır.
  • Veriyi JavaScript içine aktarmanız gerekiyorsa json_encode ile JSON_HEX_TAG ve JSON_HEX_AMP bayraklarını kullanın.

Zengin metin (HTML) kabul ediyorsanız

Blog editörü gibi HTML'e izin verilen alanlarda kaçış yapamazsınız; bunun yerine izin verilen etiket ve öznitelikleri beyaz listeyle sınırlayan bir temizleyici kullanın (ör. HTML Purifier). script, on* olay öznitelikleri ve style hiçbir zaman geçmemelidir.

İkinci savunma hattı: Content Security Policy

Bir yerde kaçış unutulsa bile CSP, tarayıcının yalnızca izin verdiğiniz kaynaklardan betik çalıştırmasını sağlar. Satır içi betik kullanmayan bir sitede şu başlık güçlü bir başlangıçtır:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
  base-uri 'self'; frame-ancestors 'none'; form-action 'self'

Oturum çerezlerini HttpOnly, Secure ve SameSite=Lax olarak işaretlemek, olası bir XSS'in oturum çalmaya dönüşmesini de zorlaştırır.

Tamamlayıcı önlemler

  • CSRF koruması: durum değiştiren her form isteğinde oturuma bağlı, tahmin edilemez bir belirteç doğrulayın.
  • Hız sınırlama: giriş, şifre sıfırlama ve iletişim formlarında IP veya oturum başına deneme sayısını sınırlayın.
  • Hata yönetimi: canlı ortamda display_errors kapalı olsun; ayrıntılı hatalar sunucu günlüğüne yazılsın, kullanıcı yalnızca genel bir mesaj görsün. Dizin yolları ve tablo adları dışarı sızmamalı.
  • Gizli bilgiler: veritabanı şifreleri ve API anahtarları koda değil, web dizini dışındaki bir .env dosyasına yazılsın.
  • Güncellemeler: PHP sürümünü ve bağımlılıkları düzenli güncelleyin; composer audit bilinen açıkları listeler.

Özet

SQL için hazırlanmış ifadeler ve beyaz liste; HTML için bağlama uygun kaçış ve temizleyici; tarayıcı için CSP ve güvenli çerezler. Bu üç katman birlikte uygulandığında, en yaygın saldırıların büyük bölümü daha kapıda durdurulur. Güvenlik sonradan eklenen bir özellik değil, ilk satırdan itibaren kodlama biçimidir.