Güvenlik Riskleri

Veritabanları çoğu zaman hassas veriler içerir ve tehlikeli işlemler yapmaya olanak tanır. Nette Database ile güvenli çalışmak için şunlar can alıcı önemdedir:

  • Güvenli ve güvensiz API'ler arasındaki farkı anlamak
  • Parametreli sorgular kullanmak
  • Girdi verisini düzgün doğrulamak

SQL Injection Nedir?

SQL injection, veritabanlarıyla çalışırken karşılaşılan en ciddi güvenlik riskidir. Temizlenmemiş kullanıcı girdisi bir SQL sorgusunun parçası olduğunda ortaya çıkar. Saldırgan kendi SQL komutlarını ekleyebilir ve böylece:

  • Verilere yetkisiz erişim sağlayabilir
  • Veritabanındaki verileri değiştirebilir ya da silebilir
  • Kimlik doğrulamayı atlayabilir
// ❌ TEHLİKELİ KOD - SQL injection'a açık
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Saldırgan şöyle bir değer girebilir: ' OR '1'='1
// Ortaya çıkan sorgu şu olurdu: SELECT * FROM users WHERE name = '' OR '1'='1'
// Bu da tüm kullanıcıları döndürür

Aynısı Database Explorer için de geçerlidir:

// ❌ TEHLİKELİ KOD - SQL injection'a açık
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Parametreli Sorgular

SQL injection'a karşı temel savunma parametreli sorgulardır. Nette Database onları kullanmanın çeşitli yollarını sunar.

En basit yol, soru işareti yer tutucuları kullanmaktır:

// ✅ Güvenli parametreli sorgu
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Explorer'da güvenli koşul
$table->where('name = ?', $name);

Bu, Database Explorer içinde soru işareti yer tutucuları ve parametrelerle ifade eklemeye izin veren tüm diğer metotlar için de geçerlidir.

INSERT, UPDATE komutlarında ya da WHERE yan tümcesinde değerleri bir dizide aktarabiliriz:

// ✅ Güvenli INSERT
$database->query('INSERT INTO users', [
	'name' => $name,
	'email' => $email,
]);

// ✅ Explorer'da güvenli INSERT
$table->insert([
	'name' => $name,
	'email' => $email,
]);

Parametre Değerlerinin Doğrulanması

Parametreli sorgular güvenli veritabanı çalışmasının temel taşıdır. Ancak onlara koyduğumuz değerler birkaç düzey denetimden geçmelidir:

Tür Denetimi

En önemlisi, parametrelerin veri türünün doğru olmasını sağlamaktır; bu, Nette Database'in güvenli kullanımının zorunlu koşuludur. Veritabanı, tüm girdi verilerinin ilgili sütuna karşılık gelen doğru veri türünde olduğunu varsayar.

Örneğin önceki örneklerdeki $name, dize yerine beklenmedik biçimde bir dizi olsaydı, Nette Database tüm öğelerini SQL sorgusuna koymaya çalışır ve bu hataya yol açardı. Bu yüzden $_GET, $_POST ya da $_COOKIE içinden gelen doğrulanmamış verileri veritabanı sorgularında asla doğrudan kullanmayın.

Biçim Doğrulaması

İkinci düzeyde verinin biçimini denetleriz; örneğin dizelerin UTF-8 kodlamasında olup olmadığını ve uzunluklarının sütun tanımına uyup uymadığını ya da sayısal değerlerin ilgili sütunun veri türü için izin verilen aralıkta olup olmadığını.

Bu doğrulama düzeyinde kısmen veritabanının kendisine güvenebiliriz; pek çok veritabanı geçersiz veriyi reddeder. Ancak davranış değişebilir; bazıları uzun dizeleri sessizce kısaltabilir ya da aralık dışındaki sayıları kırpabilir.

Alana Özgü Doğrulama

Üçüncü düzey, uygulamanıza özgü mantıksal denetimleri içerir. Örneğin seçim kutularından gelen değerlerin sunulan seçeneklerle eşleşip eşleşmediğini, sayıların beklenen aralıkta olup olmadığını (örneğin yaş 0–150) ya da değerler arasındaki karşılıklı bağımlılıkların anlamlı olup olmadığını doğrulamak.

Önerilen Doğrulama Yöntemleri

  • Tüm girdilerin düzgün doğrulanmasını otomatik olarak sağlayan Nette Forms kullanın.
  • Presenter'ları kullanın ve action*() ile render*() metotlarında parametrelerin veri türlerini belirtin.
  • Ya da filter_var() gibi standart PHP araçlarıyla kendi doğrulama katmanınızı yazın.

Sütunlarla Güvenli Çalışma

Önceki bölümde parametre değerlerinin nasıl düzgün doğrulanacağını gösterdik. Ancak SQL sorgularında dizi kullanırken anahtarlarına da aynı ölçüde dikkat etmeliyiz.

// ❌ TEHLİKELİ KOD - dizideki anahtarlar temizlenmemiş
$database->query('INSERT INTO users', $_POST);

INSERT ve UPDATE komutlarında bu kritik bir güvenlik kusurudur; saldırgan veritabanındaki herhangi bir sütunu ekleyebilir ya da değiştirebilir. Örneğin is_admin = 1 yapabilir ya da hassas sütunlara istediği veriyi koyabilir (Mass Assignment Vulnerability denen açık).

WHERE koşullarında bu daha da tehlikelidir; çünkü operatör içerebilirler:

// ❌ TEHLİKELİ KOD - dizideki anahtarlar temizlenmemiş
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// WHERE (`salary` > 100000) sorgusunu çalıştırır

Saldırgan bu yaklaşımı, çalışanların maaşlarını dizgeli biçimde keşfetmek için kullanabilir. Örneğin 100.000 üzerindeki maaşlar için bir sorguyla başlayabilir, sonra 50.000 altındakilerle sürdürebilir ve aralığı yavaş yavaş daraltarak tüm çalışanların yaklaşık maaşlarını ortaya çıkarabilir. Bu tür saldırıya SQL enumeration denir.

where() ve whereOr() metotları çok daha esnektir ve anahtarlarda ile değerlerde operatörler ve fonksiyonlar dahil SQL ifadelerini destekler. Bu da saldırgana SQL injection yapma olanağı verir:

// ❌ TEHLİKELİ KOD - saldırgan kendi SQL'ini ekleyebilir
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// WHERE (0) UNION SELECT name, salary FROM users WHERE (1) sorgusunu çalıştırır

Bu saldırı, 0) ile özgün koşulu sonlandırır, UNION ile kendi SELECT sorgusunu ekleyerek users tablosundan hassas veriler alır ve WHERE (1) ile söz dizimi açısından doğru sorguyu kapatır.

Sütun Beyaz Listesi

Sütun adlarıyla güvenli çalışmak için, kullanıcının yalnızca izin verilen sütunlarla çalışabilmesini ve kendi sütununu ekleyememesini sağlayan bir düzeneğe ihtiyacımız var. Tehlikeli sütun adlarını saptayıp engellemeyi (kara liste) deneyebilirdik, ama bu yaklaşım güvenilmezdir; saldırgan her zaman öngörmediğimiz, tehlikeli bir sütun adını yazmanın yeni bir yolunu bulabilir.

Bu yüzden mantığı tersine çevirip izin verilen sütunların açık bir listesini (beyaz liste) tanımlamak çok daha güvenlidir:

// Kullanıcının değiştirmesine izin verilen sütunlar
$allowedColumns = ['name', 'email', 'active'];

// İzin verilmeyen tüm sütunları girdiden çıkar
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));

// ✅ Artık sorgularda güvenle kullanılabilir, örneğin:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);

Dinamik Tanımlayıcılar

Dinamik tablo ve sütun adları için ?name yer tutucusunu kullanın. Bu, tanımlayıcıların ilgili veritabanının söz dizimine göre düzgün kaçışlanmasını sağlar (örneğin MySQL'de ters tırnaklarla):

// ✅ Güvenilir tanımlayıcıların güvenli kullanımı
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// MySQL'de sonuç: SELECT `name` FROM `users`

Önemli: ?name simgesini yalnızca uygulama kodunda tanımlanmış güvenilir değerler için kullanın. Kullanıcıdan gelen değerlerde yine bir beyaz liste kullanın. Aksi hâlde kendinizi güvenlik risklerine açarsınız:

// ❌ TEHLİKELİ - kullanıcı girdisini asla kullanmayın
$database->query('SELECT ?name FROM users', $_GET['column']);
versiyon: 4.x