İçeriğe geç
Educora
İleri22 dk9 / 10

Formlar, oturumlar ve güvenlik

Form verilerini $_GET ve $_POST ile al, doğrula ve htmlspecialchars ile XSS'e karşı koru; oturumlar ve çerezlerle çalış, dosya yükle, parolaları özetle (hash) ve CSRF belirteçlerini uygula.

Kendini test et
Bu derste öğreneceklerin
  • Süper global dizilerden form verisini okumak, sunucuda doğrulamak ve POST'tan sonra yönlendirmek
  • htmlspecialchars ile XSS'i önlemek, oturumları ve çerezleri güvenle kullanmak
  • Parolaları password_hash ile saklamak, CSRF belirteci uygulamak ve yüklenen dosyaları denetlemek

İletişim formu, giriş sayfası, alışveriş sepeti... Bir sitenin canlı kısmı kullanıcılardan gelen verilerle çalışır. Bu verilere ise asla körü körüne güvenilmemelidir: biri yanlışlıkla hatalı yazabilir, bir başkası da bilerek zararlı hâle getirebilir. Bu derste PHP'nin web özelliklerini ve her web geliştiricisinin bilmesi gereken temel korunma yöntemlerini öğreneceksin.

Süper global diziler

PHP, istekle ilgili bilgileri özel dizilere yerleştirir. Bunlar kodun her yerinde, hatta fonksiyonların içinde bile görünür; bu yüzden süper global diye adlandırılır. Kural şudur: yalnızca veri okuyan istekler (arama, filtreler) GET ile, veriyi değiştiren istekler (giriş, sipariş, yorum) ise POST ile gönderilir.

DiziNe içerir
$_GETadresteki parametreler: search.php?q=php → $_GET['q']
$_POSTmethod="post" ile gönderilen formun alanları
$_SERVERistekle ilgili bilgiler, ör. $_SERVER['REQUEST_METHOD']
$_COOKIE, $_SESSIONtarayıcının gönderdiği çerezler ve oturum verileri
$_FILESyüklenen dosyalar

XSS ve htmlspecialchars

Bir ziyaretçi defterinde birinin adının yerine bir <script> etiketi yazdığını düşün. Onu sayfaya olduğu gibi yazdırırsan bu betik her ziyaretçinin tarayıcısında çalışır: çerezleri çalabilir ya da sayfayı değiştirebilir. Bu saldırıya XSS (cross-site scripting) denir. Korunma yolu basittir: kullanıcıdan gelen her metni HTML'e koymadan önce htmlspecialchars ile kaçışla (escape).

Savunmasız: XSS mümkün
<p>Hello, <?= $_GET['name'] ?>!</p>
Güvenli
<p>Hello, <?= htmlspecialchars($_GET['name'] ?? '', ENT_QUOTES, 'UTF-8') ?>!</p>
?name=<script>...</script> adresiyle soldaki sayfa betiği çalıştırır, sağdaki ise onu düz metin olarak gösterir.
PHP
<?php

$input = '<script>alert("hacked")</script>';
echo htmlspecialchars($input), "\n";

function e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
}

echo e("Tom & Jerry's \"show\""), "\n";
Beklenen çıktı
&lt;script&gt;alert(&quot;hacked&quot;)&lt;/script&gt;
Tom &amp; Jerry&#039;s &quot;show&quot;

Formu işleme ve doğrulama

Tarayıcıdaki required gibi denetimler yalnızca kolaylık içindir; kolayca atlatılabilir. Asıl doğrulama sunucuda yapılır. Başarılı bir POST'tan sonra header('Location: ...') ile başka bir sayfaya yönlendir ve exit yaz. Bu, PRG (Post/Redirect/Get) desenidir: kullanıcı sayfayı yenilediğinde form ikinci kez gönderilmez.

PHP
<?php
session_start();

$errors = [];
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (mb_strlen($name) < 2) {
        $errors[] = 'Name is too short';
    }
    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        $errors[] = 'Email is not valid';
    }

    if ($errors === []) {
        // save the message to the database here
        $_SESSION['flash'] = "Thanks, $name!";
        header('Location: /contact.php');
        exit;
    }
}
// below: the HTML form shows $errors and refills the fields with e($name)
contact.php: formun işlenmesi (web sunucusu gerekir)

Doğrulama için filter_var fonksiyonu kullanışlıdır: geçerli bir değer için değerin kendisini (gerekirse doğru türe dönüştürülmüş olarak), geçersiz bir değer için ise false döndürür. Onu terminalde de deneyebilirsin:

PHP
<?php

var_dump(filter_var("aysel@example.com", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("aysel@", FILTER_VALIDATE_EMAIL));
var_dump(filter_var("42", FILTER_VALIDATE_INT));
var_dump(filter_var("200", FILTER_VALIDATE_INT, [
    "options" => ["min_range" => 1, "max_range" => 120],
]));
Beklenen çıktı
string(17) "aysel@example.com"
bool(false)
int(42)
bool(false)

Oturumlar ve çerezler

HTTP “durumsuzdur”: her istek bir öncekinden habersizdir. Kullanıcıyı tanımanın iki yolu vardır. Çerez (cookie), tarayıcıda saklanan ve her istekle sunucuya geri gönderilen küçük bir veridir. Oturum (session) ise veriyi sunucuda tutar, tarayıcıya yalnızca oturum kimliği (PHPSESSID çerezi) verilir. session_start() ve setcookie() HTTP üst bilgileri gönderdiği için sayfaya herhangi bir metin yazdırılmadan önce çağrılmalıdır.

PHP
<?php
session_start();

$_SESSION['views'] = ($_SESSION['views'] ?? 0) + 1;

setcookie('theme', 'dark', [
    'expires' => time() + 60 * 60 * 24 * 30,
    'path' => '/',
    'httponly' => true,
    'samesite' => 'Lax',
]);

$theme = $_COOKIE['theme'] ?? 'light';
echo "Views this session: {$_SESSION['views']}, theme: $theme";
İlk ziyarette: Views this session: 1, theme: light. Yeniledikten sonra: 2, theme: dark — çerez ancak bir sonraki istekle gelir.
  • httponly, çerezi JavaScript'ten gizler; XSS olsa bile çalınması zorlaşır. HTTPS kullanan bir sitede 'secure' => true da ekle.
  • Kullanıcı giriş yaptığında session_regenerate_id(true) çağır, sonra $_SESSION['user_id'] değerini yaz; eski kimlik işe yaramaz hâle gelir.
  • Çıkışta $_SESSION = []; ve session_destroy(); yaz.
  • Çerezde gizli bilgi tutma: kullanıcı onu görebilir ve değiştirebilir. Gizli veriler oturumda kalır.

Dosya yükleme

Dosya gönderen bir formda method="post" ve enctype="multipart/form-data" bulunmalıdır. PHP dosyayı geçici bir klasöre koyar, bilgilerini ise $_FILES içine yazar. Dosyanın boyutunu ve gerçek türünü denetle, ona rastgele yeni bir ad ver ve ancak ondan sonra move_uploaded_file ile taşı.

PHP
<?php

$file = $_FILES['photo'] ?? null;

if ($file && $file['error'] === UPLOAD_ERR_OK) {
    if ($file['size'] > 2 * 1024 * 1024) {
        exit('File is larger than 2 MB');
    }
    $type = mime_content_type($file['tmp_name']);
    $allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png'];
    if (!isset($allowed[$type])) {
        exit('Only JPG and PNG images are allowed');
    }
    $newName = bin2hex(random_bytes(8)) . '.' . $allowed[$type];
    move_uploaded_file($file['tmp_name'], __DIR__ . '/uploads/' . $newName);
    echo 'Uploaded!';
}
upload.php: form bir <input type="file" name="photo"> alanı gönderir

Parolalar ve CSRF

Parolaları asla açık metin olarak ya da md5, sha1 ile saklama; bu özetler çok hızlı kırılır. password_hash, parolayı bilerek yavaş tasarlanmış bir algoritmayla (şu anda bcrypt) ve rastgele bir “tuzla” (salt) özetler, password_verify ise doğrular. Özet her çağrıda farklı çıkar; bu yüzden özetler === ile karşılaştırılamaz. Veritabanında özet için VARCHAR(255) bir sütun ayır.

PHP
<?php

$hash = password_hash("secret123", PASSWORD_DEFAULT);

var_dump(password_verify("secret123", $hash));
var_dump(password_verify("Secret123", $hash));
var_dump($hash === password_hash("secret123", PASSWORD_DEFAULT));
Beklenen çıktı
bool(true)
bool(false)
bool(false)

Bir CSRF (cross-site request forgery) saldırısında başka bir site, kullanıcının tarayıcısı adına senin sitene bir form gönderir; örneğin parolasını değiştirmek için. Tarayıcı oturum çerezini otomatik olarak eklediğinden istek “meşru” görünür. Korunmak için oturumda rastgele bir belirteç (token) oluşturulur, her POST formuna gizli bir alan olarak konur ve gelen istekte denetlenir.

PHP
<?php
session_start();

// 1. create a token once per session
$_SESSION['csrf'] ??= bin2hex(random_bytes(32));

// 2. every POST form gets a hidden field named csrf with this value

// 3. check the token when a form is submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $sent = $_POST['csrf'] ?? '';
    if (!hash_equals($_SESSION['csrf'], $sent)) {
        http_response_code(403);
        exit('Invalid CSRF token');
    }
    // the form is safe to process
}
Formda: <input type="hidden" name="csrf" value="<?= e($_SESSION['csrf']) ?>">

Önemli noktalar

  • $_GET adres parametrelerini, $_POST form alanlarını tutar; veriyi değiştiren işlemler POST ile gönderilir.
  • Kullanıcıdan gelen her metni HTML'e koymadan önce htmlspecialchars ile kaçışla; bu, XSS'i önler.
  • Doğrulama sunucuda yapılır (filter_var, türler); başarılı bir POST'tan sonra header('Location: ...') ve exit ile yönlendir.
  • Oturum verisi sunucuda, çerez ise tarayıcıda saklanır; session_start() her çıktıdan önce çağrılmalıdır.
  • Parolalar password_hash/password_verify ile, formlar CSRF belirteciyle, yüklemeler ise boyut ve gerçek tür denetimiyle korunur.

Kendini test et

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

1 / 10
Kullanıcının adını XSS riski olmadan sayfaya yazdırmak için hangi fonksiyon kullanılır?