Εάν η είσοδος του χρήστη εισαχθεί χωρίς τροποποίηση σε ένα ερώτημα SQL, τότε η εφαρμογή γίνεται ευάλωτη σε SQL injection, όπως στο ακόλουθο παράδειγμα:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
Αυτό συμβαίνει επειδή ο χρήστης μπορεί να εισάγει κάτι όπως value'); DROP TABLE table;--
, και το ερώτημα γίνεται:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Τι μπορεί να γίνει για να αποτραπεί αυτό;
Προειδοποίηση που έχει καταργηθεί: Αυτό το δείγμα κώδικα της απάντησης (όπως και το δείγμα κώδικα της ερώτησης) χρησιμοποιεί την επέκταση "MySQL" της PHP, η οποία καταργήθηκε στην PHP 5.5.0 και αφαιρέθηκε εντελώς στην PHP 7.0.0.
Προειδοποίηση ασφαλείας: Αυτή η απάντηση δεν είναι σύμφωνη με τις βέλτιστες πρακτικές ασφαλείας. Η διαφυγή είναι ανεπαρκής για την αποτροπή της έγχυσης SQL, χρησιμοποιήστε προετοιμασμένες δηλώσεις. Χρησιμοποιήστε τη στρατηγική που περιγράφεται παρακάτω με δική σας ευθύνη. (Επίσης, η
mysql_real_escape_string()
καταργήθηκε στην PHP 7).
Αν χρησιμοποιείτε μια πρόσφατη έκδοση της PHP, η επιλογή mysql_real_escape_string
που περιγράφεται παρακάτω δεν θα είναι πλέον διαθέσιμη (αν και η mysqqli::escape_string
είναι μια σύγχρονη ισοδύναμη). Αυτές τις μέρες η επιλογή mysql_real_escape_string
θα είχε νόημα μόνο για κώδικα παλαιάς τεχνολογίας σε μια παλιά έκδοση της PHP.
Έχετε δύο επιλογές - να αποφύγετε τους ειδικούς χαρακτήρες στην unsafe_variable
σας ή να χρησιμοποιήσετε ένα παραμετροποιημένο ερώτημα. Και οι δύο θα σας προστάτευαν από την έγχυση SQL. Το παραμετροποιημένο ερώτημα θεωρείται η καλύτερη πρακτική, αλλά θα χρειαστεί να αλλάξετε σε μια νεότερη επέκταση της MySQL στην PHP πριν μπορέσετε να το χρησιμοποιήσετε.
Θα καλύψουμε πρώτα τη χαμηλότερης επίπτωσης διαφυγή συμβολοσειράς.
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
Δείτε επίσης, τις λεπτομέρειες της συνάρτησης mysql_real_escape_string
.
Για να χρησιμοποιήσετε το παραμετροποιημένο ερώτημα, πρέπει να χρησιμοποιήσετε τη MySQLi και όχι τις συναρτήσεις MySQL. Για να ξαναγράψουμε το παράδειγμά σας, θα χρειαζόμασταν κάτι σαν το ακόλουθο.
<?php
$mysqli = new mysqli("server", "username", "password", "database_name");
// TODO - Check that connection was successful.
$unsafe_variable = $_POST["user-input"];
$stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");
// TODO check that $stmt creation succeeded
// "s" means the database expects a string
$stmt->bind_param("s", $unsafe_variable);
$stmt->execute();
$stmt->close();
$mysqli->close();
?>
Η βασική συνάρτηση που θα θέλετε να διαβάσετε εκεί θα είναι η mysqli::prepare
.
Επίσης, όπως πρότειναν και άλλοι, μπορεί να το βρείτε χρήσιμο/πιο εύκολο να ανεβείτε ένα επίπεδο αφαίρεσης με κάτι σαν το PDO.
Λάβετε υπόψη ότι η περίπτωση για την οποία ρωτήσατε είναι αρκετά απλή και ότι πιο σύνθετες περιπτώσεις μπορεί να απαιτούν πιο σύνθετες προσεγγίσεις. Ειδικότερα:
mysql_real_escape_string
. Σε αυτού του είδους τις περιπτώσεις, θα ήταν καλύτερα να περνάτε την είσοδο του χρήστη μέσω μιας λευκής λίστας για να διασφαλίσετε ότι μόνο 'ασφαλείς' τιμές επιτρέπονται.mysql_real_escape_string
, θα υποφέρετε από το πρόβλημα που περιγράφει ο Polynomial στα σχόλια παρακάτω. Αυτή η περίπτωση είναι πιο δύσκολη επειδή οι ακέραιοι αριθμοί δεν θα περιβάλλονται από εισαγωγικά, οπότε θα μπορούσατε να το αντιμετωπίσετε επικυρώνοντας ότι η είσοδος του χρήστη περιέχει μόνο ψηφία.Θα συνιστούσα τη χρήση του PDO (PHP Data Objects) για την εκτέλεση παραμετροποιημένων ερωτημάτων SQL.
Αυτό όχι μόνο προστατεύει από την έγχυση SQL, αλλά και επιταχύνει τα ερωτήματα.
Και με τη χρήση του PDO αντί των συναρτήσεων mysql_
, mysqli_
και pgsql_
, κάνετε την εφαρμογή σας λίγο πιο αφηρημένη από τη βάση δεδομένων, στη σπάνια περίπτωση που πρέπει να αλλάξετε πάροχο βάσης δεδομένων.
Θα μπορούσατε να κάνετε κάτι βασικό σαν αυτό:
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Αυτό δεν θα λύσει κάθε πρόβλημα, αλλά είναι ένα πολύ καλό σκαλοπάτι. Παρέλειψα προφανή στοιχεία όπως ο έλεγχος της ύπαρξης της μεταβλητής'ς, η μορφή (αριθμοί, γράμματα, κλπ.).