Če je uporabniški vnos brez sprememb vstavljen v poizvedbo SQL, postane aplikacija ranljiva za SQL injection, kot v naslednjem primeru:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
To je zato, ker lahko uporabnik vnese nekaj takega, kot je value'); DROP TABLE table;--
, in poizvedba postane:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Kaj lahko storimo, da se to ne bi zgodilo?
Odpravljeno opozorilo: Vzorčna koda tega odgovora (kot tudi vzorčna koda vprašanja) uporablja razširitev
MySQL
PHP, ki je bila v PHP 5.5.0 odpravljena, v PHP 7.0.0 pa popolnoma odstranjena.
Varnostno opozorilo: Ta odgovor ni v skladu z najboljšimi varnostnimi praksami. Escaping is inadequate to prevent SQL injection, namesto tega uporabite pripravljene stavke. Spodaj opisano strategijo uporabljajte na lastno odgovornost. (V PHP 7 je bilo odstranjeno tudi
mysql_real_escape_string()
.)
Če uporabljate novejšo različico PHP, spodaj opisana možnost mysql_real_escape_string
ne bo več na voljo (čeprav je mysqli::escape_string
sodoben ekvivalent). Danes bi bila možnost mysql_real_escape_string
smiselna le za starejšo kodo na stari različici PHP.
Imate dve možnosti - izogibanje posebnim znakom v spremenljivki unsafe_variable
ali uporabo parametrirane poizvedbe. Oboje bi vas zaščitilo pred vbrizgavanjem SQL. Parametrizirana poizvedba velja za boljšo prakso, vendar bo za njeno uporabo potrebna sprememba na novejšo razširitev MySQL v PHP.
Najprej bomo obravnavali manj vplivno izogibanje nizom.
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
Oglejte si tudi podrobnosti o funkciji mysql_real_escape_string
.
Če želite uporabiti parameterizirano poizvedbo, morate uporabiti funkcijo MySQLi in ne funkcije MySQL. Če bi želeli prepisati vaš primer, bi potrebovali nekaj takega.
<?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();
?>
Ključna funkcija, ki jo boste želeli prebrati, je mysqli::prepare
.
Kot so predlagali drugi, se vam bo morda zdelo koristno/lažje, če se povzpnete na višjo raven abstrakcije z nečim, kot je PDO.
Upoštevajte, da je primer, ki ste ga vprašali, precej preprost in da lahko bolj zapleteni primeri zahtevajo bolj zapletene pristope. Zlasti:
mysql_real_escape_string
. V takem primeru bi bilo bolje, če bi uporabniški vnos posredovali prek bele liste, s čimer bi zagotovili, da so dovoljene samo 'varne' vrednosti.mysql_real_escape_string
, boste imeli težavo, ki jo je opisal Polynomial v spodnjih komentarjih. Ta primer je težji, ker cela števila ne bi bila obdana z narekovaji, zato se lahko s tem spopadete tako, da preverite, ali uporabniški vnos vsebuje samo številke.Priporočam uporabo PDO (PHP Data Objects) za izvajanje parametriziranih poizvedb SQL.
To ne samo da ščiti pred vbrizgavanjem SQL, ampak tudi pospešuje poizvedbe.
Z uporabo PDO namesto funkcij mysql_
, mysqli_
in pgsql_
pa je vaša aplikacija nekoliko bolj abstrahirana od podatkovne zbirke, če boste morali v redkih primerih zamenjati ponudnika podatkovne zbirke.
Lahko naredite nekaj osnovnega, kot je to:
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
To ne bo rešilo vseh težav, je pa zelo dobra odskočna deska. Izpustil sem očitne elemente, kot so preverjanje obstoja spremenljivke, format (številke, črke itd.).