Κίνδυνοι ασφαλείας
Οι βάσεις δεδομένων συχνά περιέχουν ευαίσθητα δεδομένα και επιτρέπουν την εκτέλεση επικίνδυνων λειτουργιών. Για την ασφαλή εργασία με τη βάση δεδομένων Nette, οι βασικές πτυχές είναι οι εξής:
- Κατανόηση της διαφοράς μεταξύ ασφαλούς και μη ασφαλούς API
- Χρήση παραμετροποιημένων ερωτημάτων
- Σωστή επικύρωση των δεδομένων εισόδου
Τι είναι το SQL Injection;
Η έγχυση SQL είναι ο σοβαρότερος κίνδυνος ασφάλειας κατά την εργασία με βάσεις δεδομένων. Συμβαίνει όταν η μη φιλτραρισμένη είσοδος του χρήστη γίνεται μέρος ενός ερωτήματος SQL. Ένας εισβολέας μπορεί να εισάγει τις δικές του εντολές SQL και έτσι:
- Να εξάγει μη εξουσιοδοτημένα δεδομένα
- να τροποποιήσει ή να διαγράψει δεδομένα στη βάση δεδομένων
- να παρακάμψει τον έλεγχο ταυτότητας
// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε έγχυση SQL
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");
// Ένας εισβολέας μπορεί να εισάγει μια τιμή όπως: ' OR '1'='1
// Το ερώτημα που θα προκύψει θα είναι: OR '1'='1'.
// Το οποίο επιστρέφει όλους τους χρήστες
Το ίδιο ισχύει και για την Εξερεύνηση βάσης δεδομένων:
// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε έγχυση SQL
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");
Ασφαλή παραμετροποιημένα ερωτήματα
Η θεμελιώδης άμυνα κατά της έγχυσης SQL είναι τα παραμετροποιημένα ερωτήματα. Η Nette Database παρέχει διάφορους τρόπους για τη χρήση τους.
Ο απλούστερος τρόπος είναι η χρήση εντολοδόχων ερωτηματικών:
// ✅ Ασφαλές παραμετροποιημένο ερώτημα
$database->query('SELECT * FROM users WHERE name = ?', $name);
// ✅ Ασφαλής συνθήκη στον Explorer
$table->where('name = ?', $name);
Αυτό ισχύει για όλες τις άλλες μεθόδους του Database Explorer που επιτρέπουν την εισαγωγή εκφράσεων με ερωτηματικά και παραμέτρους.
Για τις ρήτρες INSERT
, UPDATE
ή WHERE
, μπορείτε να
περάσετε τιμές σε πίνακα:
// ✅ Ασφαλής ΕΙΣΑΓΩΓΗ
$database->query('INSERT INTO users', [
'name' => $name,
'email' => $email,
]);
// ✅ Ασφαλής INSERT στον Explorer
$table->insert([
'name' => $name,
'email' => $email,
]);
Επικύρωση τιμής παραμέτρου
Τα παραμετροποιημένα ερωτήματα αποτελούν τον ακρογωνιαίο λίθο της ασφαλούς εργασίας σε βάσεις δεδομένων. Ωστόσο, οι τιμές που περνούν σε αυτά πρέπει να υποβάλλονται σε διάφορα επίπεδα επικύρωσης:
Έλεγχος τύπου
Η διασφάλιση του σωστού τύπου δεδομένων των παραμέτρων είναι κρίσιμη – είναι απαραίτητη προϋπόθεση για την ασφαλή χρήση της βάσης δεδομένων Nette. Η βάση δεδομένων υποθέτει ότι όλα τα δεδομένα εισόδου έχουν τον σωστό τύπο δεδομένων που αντιστοιχεί στη στήλη.
Για παράδειγμα, αν το $name
στα προηγούμενα παραδείγματα γινόταν
απροσδόκητα πίνακας αντί για συμβολοσειρά, η Nette Database θα επιχειρούσε να
εισαγάγει όλα τα στοιχεία του στο ερώτημα SQL, με αποτέλεσμα να προκύψει
σφάλμα. Επομένως, ποτέ μην χρησιμοποιείτε μη επικυρωμένα δεδομένα
από τις διευθύνσεις $_GET
, $_POST
ή $_COOKIE
απευθείας σε
ερωτήματα βάσης δεδομένων.
Επικύρωση μορφής
Το δεύτερο επίπεδο ελέγχει τη μορφή των δεδομένων – για παράδειγμα, διασφαλίζει ότι οι συμβολοσειρές είναι κωδικοποιημένες με UTF-8 και ότι το μήκος τους αντιστοιχεί στον ορισμό της στήλης ή επαληθεύει ότι οι αριθμητικές τιμές εμπίπτουν στο επιτρεπτό εύρος για τον τύπο δεδομένων της στήλης.
Σε αυτό το επίπεδο, μπορείτε να βασιστείτε εν μέρει στην ίδια τη βάση δεδομένων – πολλές βάσεις δεδομένων απορρίπτουν τα μη έγκυρα δεδομένα. Ωστόσο, η συμπεριφορά μπορεί να διαφέρει: ορισμένες μπορεί να κόβουν σιωπηρά μεγάλες συμβολοσειρές ή να αποκόπτουν αριθμούς που βρίσκονται εκτός εύρους.
Επικύρωση ειδικού τομέα
Το τρίτο επίπεδο περιλαμβάνει λογικούς ελέγχους ειδικά για την εφαρμογή σας. Για παράδειγμα, η επαλήθευση ότι οι τιμές από τα πλαίσια επιλογής ταιριάζουν με τις διαθέσιμες επιλογές, ότι οι αριθμοί εμπίπτουν σε ένα αναμενόμενο εύρος (π.χ. ηλικία 0–150 έτη) ή ότι οι σχέσεις μεταξύ των τιμών έχουν νόημα.
Συνιστώμενες μέθοδοι επικύρωσης
- Χρησιμοποιήστε Nette Forms, τα οποία χειρίζονται αυτόματα την κατάλληλη επικύρωση όλων των εισόδων.
- Χρησιμοποιήστε παρουσιαστές και δηλώστε
τύπους δεδομένων παραμέτρων στις μεθόδους
action*()
καιrender*()
. - Ή υλοποιήστε ένα προσαρμοσμένο επίπεδο επικύρωσης χρησιμοποιώντας
τυποποιημένα εργαλεία PHP όπως το
filter_var()
.
Ασφαλής εργασία με στήλες
Στην προηγούμενη ενότητα, καλύψαμε τον τρόπο με τον οποίο μπορείτε να επικυρώνετε σωστά τις τιμές των παραμέτρων. Ωστόσο, κατά τη χρήση πινάκων σε ερωτήματα SQL, πρέπει να δίνεται εξίσου μεγάλη προσοχή στα κλειδιά τους.
// ❌ ΚΙΝΔΥΝΟΣ ΚΩΔΙΚΟΣ - τα κλειδιά του πίνακα δεν καθαρίζονται
$database->query('INSERT INTO users', $_POST);
Για τις εντολές INSERT και UPDATE, αυτό είναι ένα σημαντικό ελάττωμα
ασφαλείας – ένας εισβολέας μπορεί να εισάγει ή να τροποποιήσει
οποιαδήποτε στήλη στη βάση δεδομένων. Θα μπορούσε, για παράδειγμα, να
ορίσει τη διεύθυνση is_admin = 1
ή να εισάγει αυθαίρετα δεδομένα σε
ευαίσθητες στήλες (γνωστή ως Ευπάθεια μαζικής ανάθεσης).
Στις συνθήκες WHERE, είναι ακόμη πιο επικίνδυνο, επειδή μπορούν να περιέχουν τελεστές:
// ❌ ΚΙΝΔΥΝΟΣ ΚΩΔΙΚΟΣ - τα κλειδιά του πίνακα δεν καθαρίζονται
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// εκτελεί το ερώτημα WHERE (`salary` > 100000)
Ένας εισβολέας μπορεί να χρησιμοποιήσει αυτή την προσέγγιση για να αποκαλύψει συστηματικά τους μισθούς των εργαζομένων. Μπορεί να ξεκινήσει με ένα ερώτημα για μισθούς άνω των 100.000, στη συνέχεια κάτω των 50.000, και περιορίζοντας σταδιακά το εύρος, μπορεί να αποκαλύψει τους κατά προσέγγιση μισθούς όλων των εργαζομένων. Αυτός ο τύπος επίθεσης ονομάζεται απαρίθμηση SQL.
Οι μέθοδοι where()
και whereOr()
είναι ακόμη πιο ευέλικτες και υποστηρίζουν
εκφράσεις SQL, συμπεριλαμβανομένων τελεστών και συναρτήσεων, τόσο στα
κλειδιά όσο και στις τιμές. Αυτό δίνει σε έναν εισβολέα τη δυνατότητα
να εκτελέσει σύνθετη έγχυση SQL:
// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ο επιτιθέμενος μπορεί να εισάγει τη δική του SQL
$_POST = ['0) UNION SELECT name, salary FROM users WHERE (1'];
$table->where($_POST);
// εκτελεί το ερώτημα WHERE (0) UNION SELECT name, salary FROM users WHERE (1)
Αυτή η επίθεση τερματίζει την αρχική συνθήκη με το 0)
,
προσθέτει το δικό της SELECT
χρησιμοποιώντας το UNION
για να
αποκτήσει ευαίσθητα δεδομένα από τον πίνακα users
και κλείνει με
ένα συντακτικά σωστό ερώτημα χρησιμοποιώντας το WHERE (1)
.
Λευκή λίστα στηλών
Για ασφαλή εργασία με ονόματα στηλών, χρειάζεστε έναν μηχανισμό που να διασφαλίζει ότι οι χρήστες μπορούν να αλληλεπιδρούν μόνο με τις επιτρεπόμενες στήλες και δεν μπορούν να προσθέσουν τις δικές τους. Η προσπάθεια ανίχνευσης και αποκλεισμού επικίνδυνων ονομάτων στηλών (μαύρη λίστα) είναι αναξιόπιστη – ένας επιτιθέμενος μπορεί πάντα να βρει έναν νέο τρόπο να γράψει ένα επικίνδυνο όνομα στήλης που δεν έχετε προβλέψει.
Επομένως, είναι πολύ ασφαλέστερο να αντιστρέψετε τη λογική και να ορίσετε έναν ρητό κατάλογο επιτρεπόμενων στηλών (λευκή λίστα):
// Στήλες που επιτρέπεται να τροποποιήσει ο χρήστης
$allowedColumns = ['name', 'email', 'active'];
// Αφαίρεση όλων των μη εξουσιοδοτημένων στηλών από την είσοδο
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));
// ✅ Τώρα είναι ασφαλής η χρήση σε ερωτήματα, όπως:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);
Δυναμικά αναγνωριστικά
Για δυναμικά ονόματα πινάκων και στηλών, χρησιμοποιήστε τον
αντικαταστάτη ?name
. Αυτό διασφαλίζει τη σωστή διαφυγή των
αναγνωριστικών σύμφωνα με τη δεδομένη σύνταξη της βάσης δεδομένων (π.χ.
χρήση backticks στη MySQL):
// ✅ Ασφαλής χρήση αξιόπιστων αναγνωριστικών
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Αποτέλεσμα στη MySQL: SELECT `name` FROM `users`
Σημαντικό: Χρησιμοποιήστε το σύμβολο ?name
μόνο για αξιόπιστες
τιμές που ορίζονται στον κώδικα εφαρμογής. Για τιμές που παρέχονται
από τον χρήστη, χρησιμοποιήστε και πάλι μια λευκή
λίστα. Διαφορετικά, κινδυνεύετε με τρωτά σημεία ασφαλείας:
// ❌ ΚΙΝΔΥΝΟΣ - Ποτέ μην χρησιμοποιείτε την είσοδο χρήστη
$database->query('SELECT ?name FROM users', $_GET['column']);