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*()
verender*()
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']);