Güvenlik Riskleri

Veritabanı genellikle hassas veriler içerir ve tehlikeli işlemler yapılmasına izin verir. Nette Database ile güvenli çalışmak için şunlar önemlidir:

  • Güvenli ve tehlikeli API arasındaki farkı anlamak
  • Parametreli sorgular kullanmak
  • Giriş verilerini doğru şekilde doğrulamak

SQL Injection Nedir?

SQL injection, veritabanıyla çalışırken en ciddi güvenlik riskidir. Kullanıcıdan gelen işlenmemiş girdinin SQL sorgusunun bir parçası haline gelmesiyle ortaya çıkar. Saldırgan kendi SQL deyimlerini ekleyebilir ve böylece:

  • Verilere yetkisiz erişim sağlayabilir
  • Veritabanındaki verileri değiştirebilir veya silebilir
  • Kimlik doğrulamasını atlatabilir
// ❌ TEHLİKELİ KOD - SQL injection'a karşı savunmasız
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Saldırgan örneğin şu değeri girebilir: ' OR '1'='1
// Sonuç sorgusu şöyle olur: SELECT * FROM users WHERE name = '' OR '1'='1'
// Bu da tüm kullanıcıları döndürür

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

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

Parametreli Sorgular

SQL injection'a karşı temel savunma parametreli sorgulardır. Nette Database bunların kullanımı için birkaç yol sunar.

En basit yol yer tutucu soru işaretlerini 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, yer tutucu soru işaretleri ve parametrelerle ifadeler eklemeye izin veren Database Explorer'daki diğer tüm metotlar için geçerlidir.

INSERT, UPDATE deyimleri veya WHERE yan tümcesi için değerleri bir dizi içinde iletebiliriz:

// ✅ 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, veritabanıyla güvenli çalışmanın temel yapı taşıdır. Ancak, bunlara eklediğimiz değerlerin birkaç kontrol seviyesinden geçmesi gerekir:

Tip Kontrolü

En önemlisi, parametrelerin doğru veri tipini sağlamaktır – bu, Nette Database'in güvenli kullanımı için gerekli bir koşuldur. Veritabanı, tüm giriş verilerinin ilgili sütuna karşılık gelen doğru veri tipine sahip olduğunu varsayar.

Örneğin, önceki örneklerdeki $name beklenmedik bir şekilde bir karakter dizisi yerine bir dizi olsaydı, Nette Database tüm öğelerini SQL sorgusuna eklemeye çalışır ve bu da hataya yol açardı. Bu nedenle, asla $_GET, $_POST veya $_COOKIE'den gelen doğrulanmamış verileri doğrudan veritabanı sorgularında kullanmayın.

Format Kontrolü

İkinci seviyede, verinin formatını kontrol ederiz – örneğin, karakter dizilerinin UTF-8 kodlamasında olup olmadığını ve uzunluklarının sütun tanımına uygun olup olmadığını veya sayısal değerlerin ilgili sütun veri tipi için izin verilen aralıkta olup olmadığını kontrol ederiz.

Bu doğrulama seviyesinde, kısmen veritabanının kendisine de güvenebiliriz – birçok veritabanı geçersiz verileri reddeder. Ancak, davranış farklılık gösterebilir, bazıları uzun karakter dizilerini sessizce kısaltabilir veya aralık dışındaki sayıları kırpabilir.

Alan Kontrolü

Üçüncü seviye, uygulamanıza özgü mantıksal kontrolleri temsil eder. Örneğin, seçim kutularından gelen değerlerin sunulan seçeneklere karşılık geldiğini, sayıların beklenen aralıkta olduğunu (örn. yaş 0–150 yıl) veya değerler arasındaki karşılıklı bağımlılıkların anlamlı olduğunu doğrulamak.

Önerilen Doğrulama Yöntemleri

  • Tüm girdilerin doğru doğrulamasını otomatik olarak sağlayan Nette Formları kullanın
  • Presenter'ları kullanın ve action*() ve render*() metotlarındaki parametreler için veri tiplerini belirtin
  • Veya filter_var() gibi standart PHP araçlarını kullanarak kendi doğrulama katmanınızı uygulayın

Sütunlarla Güvenli Çalışma

Önceki bölümde, parametre değerlerini nasıl doğru bir şekilde doğrulayacağımızı gösterdik. Ancak, SQL sorgularında dizileri kullanırken, anahtarlarına da aynı özeni göstermeliyiz.

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

INSERT ve UPDATE deyimlerinde bu kritik bir güvenlik açığıdır – saldırgan veritabanına herhangi bir sütunu ekleyebilir veya değiştirebilir. Örneğin, is_admin = 1 ayarlayabilir veya hassas sütunlara rastgele veriler ekleyebilir (Mass Assignment Vulnerability olarak adlandırılır).

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

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

Saldırgan bu yaklaşımı çalışanların maaşlarını sistematik olarak keşfetmek için kullanabilir. Örneğin, 100.000'in üzerindeki maaşlar için bir sorguyla başlar, sonra 50.000'in altındakilerle ve aralığı kademeli olarak 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 ve değerlerde operatörler ve fonksiyonlar dahil olmak üzere SQL ifadelerini destekler. Bu, saldırgana SQL injection yapma imkanı 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 yürütür

Bu saldırı, orijinal koşulu 0) ile sonlandırır, users tablosundan hassas verileri almak için UNION kullanarak kendi SELECT'ini ekler ve WHERE (1) kullanarak sözdizimsel olarak doğru bir sorgu 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ütunlarını ekleyememesini sağlayan bir mekanizmaya ihtiyacımız var. Tehlikeli sütun adlarını tespit etmeye ve engellemeye çalışabiliriz (kara liste), ancak bu yaklaşım güvenilmezdir – saldırgan her zaman tehlikeli bir sütun adını öngörmediğimiz yeni bir şekilde yazabilir.

Bu nedenle, mantığı tersine çevirmek ve izin verilen sütunların açık bir listesini tanımlamak (beyaz liste) çok daha güvenlidir:

// Kullanıcının düzenleyebileceği sütunlar
$allowedColumns = ['name', 'email', 'active'];

// Girdiden tüm izin verilmeyen sütunları kaldırırız
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));

// ✅ Şimdi sorgularda güvenle kullanabiliriz, ö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 tutucu sembolünü kullanın. Bu, tanımlayıcıların ilgili veritabanının sözdizimine göre (örn. MySQL'de geri tırnaklar kullanarak) doğru bir şekilde kaçışını sağlar:

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

Önemli: ?name sembolünü yalnızca uygulama kodunda tanımlanan güvenilir değerler için kullanın. Kullanıcıdan gelen değerler için tekrar beyaz listeyi kullanın. Aksi takdirde güvenlik risklerine maruz kalırsınız:

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