Κίνδυνοι Ασφαλείας

Η βάση δεδομένων συχνά περιέχει ευαίσθητα δεδομένα και επιτρέπει την εκτέλεση επικίνδυνων λειτουργιών. Για την ασφαλή εργασία με το Nette Database είναι κρίσιμο:

  • Να κατανοήσετε τη διαφορά μεταξύ ασφαλούς και μη ασφαλούς API
  • Να χρησιμοποιείτε παραμετροποιημένα ερωτήματα
  • Να επικυρώνετε σωστά τα δεδομένα εισόδου

Τι είναι το SQL Injection;

Το SQL injection είναι ο σοβαρότερος κίνδυνος ασφαλείας κατά την εργασία με τη βάση δεδομένων. Προκύπτει όταν η μη επεξεργασμένη είσοδος από τον χρήστη γίνεται μέρος ενός ερωτήματος SQL. Ο εισβολέας μπορεί να εισάγει δικές του εντολές SQL και έτσι:

  • Να αποκτήσει μη εξουσιοδοτημένη πρόσβαση σε δεδομένα
  • Να τροποποιήσει ή να διαγράψει δεδομένα στη βάση δεδομένων
  • Να παρακάμψει τον έλεγχο ταυτότητας
// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε SQL injection
$database->query("SELECT * FROM users WHERE name = '$_GET[name]'");

// Ο εισβολέας μπορεί να εισάγει για παράδειγμα την τιμή: ' OR '1'='1
// Το τελικό ερώτημα θα είναι: SELECT * FROM users WHERE name = '' OR '1'='1'
// Το οποίο επιστρέφει όλους τους χρήστες

Το ίδιο ισχύει και για το Database Explorer:

// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ευάλωτος σε SQL injection
$table->where('name = ' . $_GET['name']);
$table->where("name = '$_GET[name]'");

Παραμετροποιημένα Ερωτήματα

Η βασική άμυνα κατά του SQL injection είναι τα παραμετροποιημένα ερωτήματα. Το Nette Database προσφέρει διάφορους τρόπους χρήσης τους.

Ο απλούστερος τρόπος είναι η χρήση placeholders ερωτηματικών (?):

// ✅ Ασφαλές παραμετροποιημένο ερώτημα
$database->query('SELECT * FROM users WHERE name = ?', $name);

// ✅ Ασφαλής συνθήκη στο Explorer
$table->where('name = ?', $name);

Αυτό ισχύει για όλες τις άλλες μεθόδους στο Database Explorer, που επιτρέπουν την εισαγωγή εκφράσεων με placeholders ερωτηματικά και παραμέτρους.

Για εντολές INSERT, UPDATE ή τη ρήτρα WHERE, μπορούμε να περάσουμε τις τιμές σε έναν πίνακα:

// ✅ Ασφαλές INSERT
$database->query('INSERT INTO users', [
	'name' => $name,
	'email' => $email,
]);

// ✅ Ασφαλές INSERT στο Explorer
$table->insert([
	'name' => $name,
	'email' => $email,
]);

Επικύρωση Τιμών Παραμέτρων

Τα παραμετροποιημένα ερωτήματα είναι ο θεμελιώδης λίθος της ασφαλούς εργασίας με τη βάση δεδομένων. Ωστόσο, οι τιμές που εισάγουμε σε αυτά πρέπει να περάσουν από διάφορα επίπεδα ελέγχου:

Έλεγχος Τύπου

Το πιο σημαντικό είναι να διασφαλιστεί ο σωστός τύπος δεδομένων των παραμέτρων – αυτό είναι απαραίτητη προϋπόθεση για την ασφαλή χρήση του Nette Database. Η βάση δεδομένων υποθέτει ότι όλα τα δεδομένα εισόδου έχουν τον σωστό τύπο δεδομένων που αντιστοιχεί στη συγκεκριμένη στήλη.

Για παράδειγμα, εάν το $name στα προηγούμενα παραδείγματα ήταν απροσδόκητα ένας πίνακας αντί για μια συμβολοσειρά, το Nette Database θα προσπαθούσε να εισάγει όλα τα στοιχεία του στο ερώτημα SQL, οδηγώντας σε σφάλμα. Επομένως, ποτέ μην χρησιμοποιείτε μη επικυρωμένα δεδομένα από $_GET, $_POST ή $_COOKIE απευθείας σε ερωτήματα βάσης δεδομένων.

Έλεγχος Μορφής

Στο δεύτερο επίπεδο, ελέγχουμε τη μορφή των δεδομένων – για παράδειγμα, εάν οι συμβολοσειρές είναι σε κωδικοποίηση UTF-8 και το μήκος τους αντιστοιχεί στον ορισμό της στήλης, ή εάν οι αριθμητικές τιμές βρίσκονται εντός του επιτρεπόμενου εύρους για τον συγκεκριμένο τύπο δεδομένων της στήλης.

Σε αυτό το επίπεδο επικύρωσης, μπορούμε εν μέρει να βασιστούμε και στην ίδια τη βάση δεδομένων – πολλές βάσεις δεδομένων απορρίπτουν μη έγκυρα δεδομένα. Ωστόσο, η συμπεριφορά μπορεί να διαφέρει, κάποιες μπορεί να περικόψουν σιωπηλά μακριές συμβολοσειρές ή να κόψουν αριθμούς εκτός εύρους.

Έλεγχος τομέα

Το τρίτο επίπεδο αντιπροσωπεύουν οι λογικοί έλεγχοι που είναι ειδικοί για την εφαρμογή σας. Για παράδειγμα, η επαλήθευση ότι οι τιμές από τα select boxes αντιστοιχούν στις προσφερόμενες επιλογές, ότι οι αριθμοί βρίσκονται στο αναμενόμενο εύρος (π.χ. ηλικία 0–150 ετών) ή ότι οι αμοιβαίες εξαρτήσεις μεταξύ των τιμών έχουν νόημα.

Συνιστώμενοι Τρόποι Επικύρωσης

  • Χρησιμοποιήστε Nette Forms, που εξασφαλίζουν αυτόματα τη σωστή επικύρωση όλων των εισόδων
  • Χρησιμοποιήστε Presenters και δηλώστε τους τύπους δεδομένων για τις παραμέτρους στις μεθόδους action*() και render*()
  • Ή υλοποιήστε το δικό σας επίπεδο επικύρωσης χρησιμοποιώντας τυπικά εργαλεία PHP όπως το filter_var()

Ασφαλής Εργασία με Στήλες

Στην προηγούμενη ενότητα, δείξαμε πώς να επικυρώνουμε σωστά τις τιμές των παραμέτρων. Ωστόσο, κατά τη χρήση πινάκων σε ερωτήματα SQL, πρέπει να δώσουμε την ίδια προσοχή και στα κλειδιά τους.

// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - τα κλειδιά στον πίνακα δεν έχουν υποστεί επεξεργασία
$database->query('INSERT INTO users', $_POST);

Στις εντολές INSERT και UPDATE, αυτό αποτελεί κρίσιμο σφάλμα ασφαλείας – ο εισβολέας μπορεί να εισάγει ή να αλλάξει οποιαδήποτε στήλη στη βάση δεδομένων. Θα μπορούσε, για παράδειγμα, να ορίσει is_admin = 1 ή να εισάγει αυθαίρετα δεδομένα σε ευαίσθητες στήλες (η λεγόμενη Mass Assignment Vulnerability).

Στις συνθήκες WHERE, είναι ακόμη πιο επικίνδυνο, επειδή μπορεί να περιέχουν τελεστές:

// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - τα κλειδιά στον πίνακα δεν έχουν υποστεί επεξεργασία
$_POST['salary >'] = 100000;
$database->query('SELECT * FROM users WHERE', $_POST);
// εκτελεί το ερώτημα WHERE (`salary` > 100000)

Ο εισβολέας μπορεί να χρησιμοποιήσει αυτή την προσέγγιση για να ανακαλύψει συστηματικά τους μισθούς των υπαλλήλων. Μπορεί να ξεκινήσει, για παράδειγμα, με ένα ερώτημα για μισθούς πάνω από 100.000, στη συνέχεια κάτω από 50.000, και με σταδιακή στένωση του εύρους, μπορεί να αποκαλύψει τους κατά προσέγγιση μισθούς όλων των υπαλλήλων. Αυτός ο τύπος επίθεσης ονομάζεται SQL enumeration.

Οι μέθοδοι where() και whereOr() είναι ακόμη πολύ πιο ευέλικτες και υποστηρίζουν εκφράσεις SQL στα κλειδιά και τις τιμές, συμπεριλαμβανομένων τελεστών και συναρτήσεων. Αυτό δίνει στον εισβολέα τη δυνατότητα να εκτελέσει SQL injection:

// ❌ ΕΠΙΚΙΝΔΥΝΟΣ ΚΩΔΙΚΑΣ - ο εισβολέας μπορεί να εισάγει δικό του 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).

Whitelist Στηλών

Για την ασφαλή εργασία με ονόματα στηλών, χρειαζόμαστε έναν μηχανισμό που να διασφαλίζει ότι ο χρήστης μπορεί να εργαστεί μόνο με τις επιτρεπόμενες στήλες και δεν μπορεί να προσθέσει δικές του. Θα μπορούσαμε να προσπαθήσουμε να ανιχνεύσουμε και να μπλοκάρουμε επικίνδυνα ονόματα στηλών (blacklist), αλλά αυτή η προσέγγιση είναι αναξιόπιστη – ο εισβολέας μπορεί πάντα να βρει έναν νέο τρόπο να γράψει ένα επικίνδυνο όνομα στήλης που δεν είχαμε προβλέψει.

Επομένως, είναι πολύ πιο ασφαλές να αντιστρέψουμε τη λογική και να ορίσουμε μια ρητή λίστα επιτρεπόμενων στηλών (whitelist):

// Στήλες που μπορεί να επεξεργαστεί ο χρήστης
$allowedColumns = ['name', 'email', 'active'];

// Φιλτράρουμε τα δεδομένα εισόδου για να κρατήσουμε μόνο τα επιτρεπόμενα κλειδιά
$filteredData = array_intersect_key($userData, array_flip($allowedColumns));

// ✅ Τώρα μπορούμε να τα χρησιμοποιήσουμε με ασφάλεια σε ερωτήματα, όπως:
$database->query('INSERT INTO users', $filteredData);
$table->update($filteredData);
$table->where($filteredData);

Δυναμικά Αναγνωριστικά

Για δυναμικά ονόματα πινάκων και στηλών, χρησιμοποιήστε το placeholder ?name. Αυτό εξασφαλίζει τη σωστή διαφυγή (escaping) των αναγνωριστικών σύμφωνα με τη σύνταξη της συγκεκριμένης βάσης δεδομένων (π.χ. χρησιμοποιώντας ανάποδα εισαγωγικά ` ` ` στην MySQL):

// ✅ Ασφαλής χρήση αξιόπιστων αναγνωριστικών
$table = 'users';
$column = 'name';
$database->query('SELECT ?name FROM ?name', $column, $table);
// Αποτέλεσμα στην MySQL: SELECT `name` FROM `users`

Σημαντικό: χρησιμοποιήστε το σύμβολο ?name μόνο για αξιόπιστες τιμές που ορίζονται στον κώδικα της εφαρμογής. Για τιμές από τον χρήστη, χρησιμοποιήστε ξανά τη whitelist. Διαφορετικά, εκτίθεστε σε κινδύνους ασφαλείας:

// ❌ ΕΠΙΚΙΝΔΥΝΟ - ποτέ μην χρησιμοποιείτε είσοδο από τον χρήστη
$database->query('SELECT ?name FROM users', $_GET['column']);
έκδοση: 4.0