- Konu sahibi
- #1
Form verisini güvenli işlemek için önce gelen alanları beklenen türe ve biçime göre doğrulayın, geçersizse kaydetme işlemine geçmeden hatayı gösterin; veritabanına yazarken hazırlanmış sorgular, parolalarda
Doğrulama, verinin kurala uyup uymadığını kontrol eder. Örneğin yaş alanının tam sayı olması, e-posta adresinin geçerli biçimde bulunması veya kullanıcı adının yalnızca izin verilen karakterleri içermesi doğrulama konusudur. Temizleme ise veriyi belirli bir kullanım için dönüştürür; ancak temizleme, geçersiz girdiyi otomatik olarak güvenli veya doğru hâle getirmez.
Bu nedenle kullanıcıdan gelen değeri önce alın, beklenen biçimde doğrulayın ve yalnızca geçerliyse işleme devam edin. Hata mesajlarında kullanıcının gönderdiği değeri doğrudan HTML içine yerleştirmeyin; yeniden göstermeniz gerekiyorsa HTML bağlamına göre kaçışlayın.
Aşağıdaki örnek varsayımsal bir kayıt formunu işler.
Kodda
Formun HTML tarafında hata mesajlarını ve eski değerleri güvenli biçimde üretin:
Form güvenliği tek bir fonksiyonla sağlanmaz. Kullanım amacına göre doğru önlem seçilmelidir.
Tabloya göre örneğin
Oturum açmış kullanıcının hesabında değişiklik yapan veya veritabanına kayıt ekleyen formlarda sunucu tarafında CSRF belirteci kontrol edin. Formu üretirken oturumda rastgele bir belirteç oluşturup gizli alana koyun; POST isteği geldiğinde formdan gelen değer ile oturumdaki değeri zamanlama saldırılarına dayanıklı karşılaştırmayla kontrol edin.
Form içinde tokenı şu şekilde gönderin:
Tokenın geçersiz olması, kullanıcının form alanlarını yanlış doldurduğu anlamına gelmez; isteğin beklenen form oturumundan gelmediğini gösterir. Bu durumda kayıt işlemini durdurun. Kullanıcıya tekrar form açtırmak gerekiyorsa yeni bir sayfa isteğiyle yeni veya geçerli oturum tokenını üretin.
Doğrulama hatası yoksa hazırlanmış sorguyu çalıştırın ve işlem başarısız olursa uygulamanın hata ayrıntılarını kullanıcıya göstermeyin. Geliştirme ortamında günlük kaydı tutulabilir; üretim ortamında SQL sorgusu, bağlantı bilgileri, parola veya tam hata yığını tarayıcıya gönderilmemelidir.
Kayıt başarılı olduğunda POST isteği sonrasında yönlendirme yapmak, sayfa yenilendiğinde formun yeniden gönderilmesini önler. Yönlendirilen başarı sayfası yalnızca “kayıt tamamlandı” mesajı göstermeli; kullanıcının gönderdiği parola veya hassas form verilerini URL parametresine taşımamalıdır.
E-posta alanının veritabanında benzersiz olması gerekiyorsa bunu yalnız PHP kontrolüne bırakmayın. Uygulama tarafındaki ön kontrol kullanıcıya hızlı hata gösterebilir, ancak aynı anda gelen iki isteği kesin olarak engellemek için veritabanında
password_hash(), HTML çıktısında ise htmlspecialchars() kullanın. Ayrıca form gönderiminde CSRF koruması uygulayın. Böylece doğrulama hataları kullanıcıya geri bildirilebilir, kötü amaçlı girdiler ise veritabanı sorgusunu veya sayfa çıktısını doğrudan etkileyemez.Doğrulama ile temizleme arasındaki fark
Doğrulama, verinin kurala uyup uymadığını kontrol eder. Örneğin yaş alanının tam sayı olması, e-posta adresinin geçerli biçimde bulunması veya kullanıcı adının yalnızca izin verilen karakterleri içermesi doğrulama konusudur. Temizleme ise veriyi belirli bir kullanım için dönüştürür; ancak temizleme, geçersiz girdiyi otomatik olarak güvenli veya doğru hâle getirmez.
Bu nedenle kullanıcıdan gelen değeri önce alın, beklenen biçimde doğrulayın ve yalnızca geçerliyse işleme devam edin. Hata mesajlarında kullanıcının gönderdiği değeri doğrudan HTML içine yerleştirmeyin; yeniden göstermeniz gerekiyorsa HTML bağlamına göre kaçışlayın.
Form verisini güvenli sırayla işleme
Aşağıdaki örnek varsayımsal bir kayıt formunu işler.
form.php dosyasına gönderilen ad, email, yas ve sifre alanlarını kontrol eder. Veritabanı bağlantısı için PDO kullanıldığı ve $pdo değişkeninin daha önce oluşturulduğu varsayılmıştır.
PHP:
<?php
declare(strict_types=1);
$hatalar = [];
$eskiVeriler = [
'ad' => '',
'email' => '',
'yas' => '',
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ad = trim((string) ($_POST['ad'] ?? ''));
$email = trim((string) ($_POST['email'] ?? ''));
$yas = trim((string) ($_POST['yas'] ?? ''));
$sifre = (string) ($_POST['sifre'] ?? '');
$eskiVeriler = [
'ad' => $ad,
'email' => $email,
'yas' => $yas,
];
if ($ad === '' || mb_strlen($ad) < 2 || mb_strlen($ad) > 100) {
$hatalar['ad'] = 'Ad 2 ile 100 karakter arasında olmalıdır.';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$hatalar['email'] = 'Geçerli bir e-posta adresi girin.';
}
if (filter_var($yas, FILTER_VALIDATE_INT) === false) {
$hatalar['yas'] = 'Yaş tam sayı olmalıdır.';
} else {
$yasSayisi = (int) $yas;
if ($yasSayisi < 18 || $yasSayisi > 120) {
$hatalar['yas'] = 'Yaş 18 ile 120 arasında olmalıdır.';
}
}
if (mb_strlen($sifre) < 12) {
$hatalar['sifre'] = 'Parola en az 12 karakter olmalıdır.';
}
if ($hatalar === []) {
$sifreHash = password_hash($sifre, PASSWORD_DEFAULT);
$sorgu = $pdo->prepare(
'INSERT INTO kullanicilar (ad, email, yas, sifre_hash)
VALUES (:ad, :email, :yas, :sifre_hash)'
);
$sorgu->execute([
'ad' => $ad,
'email' => $email,
'yas' => $yasSayisi,
'sifre_hash' => $sifreHash,
]);
header('Location: kayit-basarili.php');
exit;
}
}
function e(string $deger): string
{
return htmlspecialchars($deger, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
Kodda
$_POST değerleri doğrudan kullanılmıyor; alan bulunmadığında boş değer atanıyor, metin alanlarında baştaki ve sondaki boşluklar kaldırılıyor. Ancak trim() tek başına doğrulama değildir. Örneğin e-posta için ayrıca FILTER_VALIDATE_EMAIL, yaş için tam sayı ve aralık kontrolü uygulanıyor.filter_var() ile yapılan kontrolün sonucu yalnızca biçimin uygun olduğunu gösterir. E-posta adresinin gerçekten kullanıcıya ait olduğunu veya kişinin belirtilen yaşı taşıdığını kanıtlamaz. Böyle bir kanıt gerekiyorsa ayrıca e-posta doğrulama bağlantısı, kimlik doğrulama veya iş kurallarına uygun başka bir süreç gerekir.Hatalı sonuçta ne yapılır?
$hatalar dizisi boş değilse form veritabanına yazılmaz. Kullanıcıya hangi alanın neden kabul edilmediği gösterilir; geçerli alanların değerleri yeniden sunulabilir. Parola gibi hassas değerler yeniden forma yazılmamalı ve hata çıktısında gösterilmemelidir.Formun HTML tarafında hata mesajlarını ve eski değerleri güvenli biçimde üretin:
HTML:
<input type="text" name="ad" value="<?= e($eskiVeriler['ad']) ?>">
<?php if (isset($hatalar['ad'])): ?>
<p><?= e($hatalar['ad']) ?></p>
<?php endif; ?>
<input type="email" name="email" value="<?= e($eskiVeriler['email']) ?>">
<?php if (isset($hatalar['email'])): ?>
<p><?= e($hatalar['email']) ?></p>
<?php endif; ?>
htmlspecialchars() burada HTML özniteliği ve metin içeriği için çıktıyı kaçışlar. Bu işlem veriyi veritabanına kaydetmeden önce yapılacak genel bir “temizleme” yöntemi değildir; HTML çıktısı oluşturulurken uygulanır. Aynı veri JavaScript, URL, SQL veya CSS içinde kullanılacaksa o bağlama uygun yöntem gerekir.Her katmanda farklı güvenlik önlemi kullanın
Form güvenliği tek bir fonksiyonla sağlanmaz. Kullanım amacına göre doğru önlem seçilmelidir.
| İşlem | Doğru yaklaşım | Neyi sağlar? |
|---|---|---|
| Alan doğrulama | Tür, uzunluk, biçim ve aralık kontrolü | Beklenmeyen veya eksik veriyi reddeder |
| SQL sorgusu | PDO hazırlanmış sorguları ve parametreleri kullanma | Girdinin SQL komutu olarak yorumlanmasını önler |
| HTML çıktısı | Bağlama uygun çıktı kaçışlama, HTML için htmlspecialchars() | XSS riskini azaltır |
| Parola saklama | password_hash() kullanma | Parolayı düz metin olarak saklamaz |
| Form gönderimi | CSRF belirteci doğrulama | Başka bir siteden kullanıcının oturumu üzerinden istenmeyen işlem yapılmasını önlemeye yardımcı olur |
htmlspecialchars() SQL enjeksiyonunu çözmez; hazırlanmış sorgular da HTML çıktısındaki XSS riskini çözmez. Her veri, kullanıldığı bağlama göre ayrıca korunmalıdır.CSRF korumasını ekleme
Oturum açmış kullanıcının hesabında değişiklik yapan veya veritabanına kayıt ekleyen formlarda sunucu tarafında CSRF belirteci kontrol edin. Formu üretirken oturumda rastgele bir belirteç oluşturup gizli alana koyun; POST isteği geldiğinde formdan gelen değer ile oturumdaki değeri zamanlama saldırılarına dayanıklı karşılaştırmayla kontrol edin.
PHP:
<?php
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$gelenToken = (string) ($_POST['csrf_token'] ?? '');
if (!hash_equals($_SESSION['csrf_token'], $gelenToken)) {
http_response_code(403);
exit('Geçersiz form isteği.');
}
}
Form içinde tokenı şu şekilde gönderin:
HTML:
<input type="hidden" name="csrf_token" value="<?= e($_SESSION['csrf_token']) ?>">
Tokenın geçersiz olması, kullanıcının form alanlarını yanlış doldurduğu anlamına gelmez; isteğin beklenen form oturumundan gelmediğini gösterir. Bu durumda kayıt işlemini durdurun. Kullanıcıya tekrar form açtırmak gerekiyorsa yeni bir sayfa isteğiyle yeni veya geçerli oturum tokenını üretin.
Kayıt başarılı olduktan sonra sonucu kontrol etme
Doğrulama hatası yoksa hazırlanmış sorguyu çalıştırın ve işlem başarısız olursa uygulamanın hata ayrıntılarını kullanıcıya göstermeyin. Geliştirme ortamında günlük kaydı tutulabilir; üretim ortamında SQL sorgusu, bağlantı bilgileri, parola veya tam hata yığını tarayıcıya gönderilmemelidir.
Kayıt başarılı olduğunda POST isteği sonrasında yönlendirme yapmak, sayfa yenilendiğinde formun yeniden gönderilmesini önler. Yönlendirilen başarı sayfası yalnızca “kayıt tamamlandı” mesajı göstermeli; kullanıcının gönderdiği parola veya hassas form verilerini URL parametresine taşımamalıdır.
E-posta alanının veritabanında benzersiz olması gerekiyorsa bunu yalnız PHP kontrolüne bırakmayın. Uygulama tarafındaki ön kontrol kullanıcıya hızlı hata gösterebilir, ancak aynı anda gelen iki isteği kesin olarak engellemek için veritabanında
UNIQUE kısıtı da bulunmalıdır. Kısıt ihlalinde sorgu hatasını yakalayıp e-posta adresinin zaten kullanıldığını belirten genel bir hata döndürün.Sık yapılan hatalar
$_POST['alan']değerini kontrol etmeden kullanmak: Alan hiç gönderilmezse uyarı veya hatalı işleme oluşabilir.- Yalnız istemci tarafı JavaScript doğrulamasına güvenmek: İstemci kontrolleri devre dışı bırakılabildiği için sunucu tarafı doğrulama zorunludur.
- SQL sorgusunu string birleştirmeyle oluşturmak: Kullanıcı verisi SQL komutuna karışabilir; hazırlanmış sorgu kullanın.
- Parolayı düz metin veya geri döndürülebilir şifreleme ile saklamak: Parola için
password_hash()ve giriş kontrolündepassword_verify()kullanın. - Hata mesajını doğrudan HTML’e yazmak: Kullanıcı girdisini çıktı bağlamına uygun şekilde kaçışlayın.
- Geçersiz girdiyi sessizce düzeltip kaydetmek: Kullanıcıya hangi kuralın uygulanamadığını bildirin; aksi hâlde kaydedilen veri beklenenden farklı olabilir.