Jei į SQL užklausą be pakeitimų įterpiama vartotojo įvestis, programa tampa pažeidžiama SQL injekcija, kaip toliau pateiktame pavyzdyje:
$unsafe_variable = $_POST['user_input'];
mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");
Taip yra todėl, kad naudotojas gali įvesti kažką panašaus į value'); DROP TABLE table;--
, ir užklausa tampa:
INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')
Ką galima padaryti, kad taip neatsitiktų?
Panaikintas įspėjimas: Šiame atsakymo'kodo pavyzdyje (kaip ir klausimo'kodo pavyzdyje) naudojamas PHP plėtinys
MySQL
, kuris PHP 5.5.0 buvo panaikintas PHP 5.5.0, o PHP 7.0.0 buvo visiškai pašalintas.
Įspėjimas dėl saugumo: Šis atsakymas neatitinka geriausios saugumo praktikos. Escaping is insuquate to prevent SQL injection, vietoj to naudokite prepared statements. Toliau aprašytą strategiją naudokite savo rizika. (Be to,
mysql_real_escape_string()
buvo pašalinta PHP 7.)
Jei naudojate naujesnę PHP versiją, toliau aprašyta mysql_real_escape_string
parinktis nebeveiks (nors mysqli::escape_string
yra modernus atitikmuo). Šiais laikais mysql_real_escape_string
parinktį būtų prasminga naudoti tik senosios PHP versijos kodui.
Turite dvi galimybes - ištrūkti iš unsafe_variable
specialiųjų simbolių arba naudoti parametrinę užklausą. Abu būdai apsaugotų jus nuo SQL injekcijos. Parametrizuota užklausa laikoma geresne praktika, tačiau prieš ją naudojant reikės pakeisti PHP į naujesnį "MySQL" plėtinį.
Pirmiausia aptarsime mažesnio poveikio eilutės išvengimo būdą.
//Connect
$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
//Disconnect
Taip pat žr. išsamią informaciją apie mysql_real_escape_string
funkciją.
Norėdami naudoti parametrizuotą užklausą, turite naudoti MySQLi, o ne MySQL funkcijas. Norint perrašyti jūsų pavyzdį, mums reikėtų kažko panašaus į tai, kas išdėstyta toliau.
<?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();
?>
Pagrindinė funkcija, apie kurią norėsite paskaityti, būtų mysqli::prepare
.
Be to, kaip siūlė kiti, jums gali būti naudinga ir (arba) paprasčiau pakelti abstrakcijos lygmenį, naudojant ką nors panašaus į PDO.
Atkreipkite dėmesį, kad atvejis, apie kurį klausėte, yra gana paprastas, o sudėtingesniems atvejams gali prireikti sudėtingesnių metodų. Visų pirma:
mysql_real_escape_string
. Tokiu atveju geriau naudotojo įvestį perduoti per baltąjį sąrašą, kad būtų užtikrinta, jog įvestos tik 'saugios' reikšmės.mysql_real_escape_string
metodą, susidursite su problema, kurią aprašė Polynomial tolesniuose komentaruose. Šis atvejis yra sudėtingesnis, nes sveikieji skaičiai nebūtų apsupti kabutėmis, todėl galėtumėte spręsti problemą patvirtindami, kad naudotojo įvesties įraše yra tik skaitmenys.Parametrizuotoms SQL užklausoms atlikti rekomenduočiau naudoti PDO (PHP duomenų objektai).
Tai ne tik apsaugo nuo SQL injekcijos, bet ir pagreitina užklausas.
Be to, naudodami PDO, o ne mysql_
, mysqli_
ir pgsql_
funkcijas, šiek tiek labiau abstrahuosite savo programą nuo duomenų bazės, jei retais atvejais tektų keisti duomenų bazės tiekėją.
Galite padaryti kažką panašaus į tai:
$safe_variable = mysqli_real_escape_string($_POST["user-input"], $dbConnection);
mysqli_query($dbConnection, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");
Tai neišspręs visų problemų, bet tai labai geras žingsnis. Neįtraukiau akivaizdžių dalykų, tokių kaip kintamojo egzistavimo tikrinimas, formatas (skaičiai, raidės ir t. t.).