Kaip išvengti SQL injekcijos PHP?

Jei naudotojo įvestis įterpiama nekeičiant SQL užklausos, programa tampa pažeidžiama SQL injekcijai , kaip nurodyta sekančiame pavyzdyje:

13 сент. nustatė Andrew G. Johnson rugsėjo 13 d 2008-09-13 02:55 '08 at 2:55 2008-09-13 02:55
@ 28 atsakymai

Naudokite paruoštus pareiškimus ir parametrų užklausas. Tai yra SQL pareiškimai, kuriuos duomenų bazių serveris siunčia ir analizuoja atskirai nuo bet kokių parametrų. Taigi užpuolikas negali švirkšti kenkėjiško SQL.

Iš esmės turite dvi galimybes:

  • Naudojant SKVN (bet kuriam palaikomam duomenų bazės tvarkytuvui):

    MySQLi“ naudojimas („MySQL“): 

     $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 

    Pirmiau pateiktame pavyzdyje klaidos režimas nėra būtinas , tačiau rekomenduojama jį įtraukti . Taigi, scenarijus nebebus sustabdomas su „ Fatal Error kai kažkas negerai. Ir tai suteikia kūrėjui galimybę catch bet kokias klaidas, kurios PDOException kaip PDOException s.

    reikia , bet tai yra pirmoji eilutė setAttribute() , kuri informuoja SKVN apie emuliuojamų parengtų ataskaitų išjungimą ir realių parengtų pareiškimų naudojimą. Tai užtikrina, kad operatorius ir vertybės neišspręstų PHP prieš siunčiant į „MySQL“ serverį (leidžiančią užpuolikui įterpti kenkėjišką SQL).

    Nors galite nustatyti charset konstruktoriaus parametruose, svarbu pažymėti, kad senesnės PHP (<5.3.6) versijos tyliai ignoruoja DSN parametrų parametrus .

    Aprašymas

    Taip atsitinka, kad duomenų bazės serveris analizuoja ir kompiliuoja jūsų parengtą SQL. Nurodydami parametrus (arba ? Arba pavadintą parametrą, pvz :name aukščiau pateiktame pavyzdyje), nurodote duomenų bazės variklį, į kurį norite įjungti filtrą. Tada, kai skambinate execute , parengta ataskaita derinama su jūsų nurodytomis parametrų reikšmėmis.

    Svarbu, kad parametrų reikšmės būtų derinamos su kompiliuojamu pareiškimu, o ne su SQL seka. SQL įpurškimas suklaidina scenarijų, įskaitant kenksmingas eilutes, kai jis sukuria SQL, kad jis galėtų siųsti į duomenų bazę. Todėl, siunčiant faktinį SQL atskirai nuo parametrų, galite apriboti riziką, kad nebaigsite atlikti. Visi parametrai, kuriuos siunčiate naudodami paruoštą ataskaitą, bus traktuojami tik kaip stygos (nors duomenų bazės variklis gali atlikti tam tikrą optimizavimą, todėl, žinoma, parametrai taip pat gali būti rodomi kaip numeriai). Pirmiau pateiktame pavyzdyje, jei kintamasis $name yra 'Sarah'; DELETE FROM employees 'Sarah'; DELETE FROM employees , rezultatas bus tik eilutės "'Sarah'; DELETE FROM employees" , ir jūs negausite tuščios lentelės .

    Kitas naudotas paruoštų pareiškimų privalumas yra tas, kad jei tą pačią sesiją atliksite tą patį pareiškimą, jis bus apdorojamas tik vieną kartą, o tai suteikia jums šiek tiek greičio prieaugio.

    O, ir kadangi jūs paklausėte, kaip tai padaryti įterpimui, čia yra pavyzdys (naudojant SKVN):

    8265
13 сент. Atsakymą pateikė Theo 13 Sep. 2008-09-13 15:30 '08, 15:30, 2008-09-13 15:30

Įspėjimas Šiame pavyzdžio atsakymo kode (pavyzdžiui, pavyzdinio kodo kodo) naudojamas PHP MySQL plėtinys, kuris PHP 5.5.0 yra pasenęs ir visiškai pašalintas PHP 7.0.0.

Jei naudojate naujausią PHP mysql_real_escape_string , žemiau esanti parinktis „ mysql_real_escape_string , „ mysql_real_escape_string nebebus (nors „ mysqli::escape_string yra modernus atitikmuo). Šiomis dienomis mysql_real_escape_string parinktis prasminga tik pasenusiam kodui senoje PHP versijoje.


Turite dvi parinktis - pabėgti nuo specialių simbolių unsafe_variable arba naudojant parametruotą užklausą. Abu jus apsaugo nuo SQL injekcijos. Parametriška užklausa laikoma geriausia praktika, tačiau prieš naudodami ją turėsite pereiti prie naujesnio MySQL plėtinio PHP.

Mes apimsime žemesnę šoko eilutę, vengdami pirmojo.

 //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. mysql_real_escape_string .

Norint naudoti parametruojamą užklausą, turite naudoti „ MySQLi“, o ne „ MySQL“ funkcijas. Jei norite perrašyti savo pavyzdį, mums reikia kažko panašaus.

 <?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, kurią norite skaityti, yra mysqli::prepare .

Be to, kaip siūlo kiti, jums gali būti naudinga / lengviau padidinti abstrakcijos lygį su kažkuo panašiu į SKVN .

Atkreipkite dėmesį, kad jūsų prašomas atvejis yra gana paprastas ir kad sudėtingesniais atvejais gali prireikti sudėtingesnių metodų. Ypač:

  • Jei norite keisti SQL struktūrą, pagrįstą vartotojo įvedimu, parametrinės užklausos nepadės, o reikalingas mysql_real_escape_string neapima mysql_real_escape_string . Tokiu atveju, naudodamiesi baltuoju sąrašu, naudotojo įvestą informaciją reikia geriau perduoti, kad įsitikintumėte, jog praleidžiama tik „saugios“ reikšmės.
  • Jei naudosite sveikasis skaičius iš vartotojo įvesties būsenoje ir naudosite mysql_real_escape_string metodą, jūs nukentėsite nuo mysql_real_escape_string aprašytos problemos toliau pateiktose pastabose. Šis atvejis yra sudėtingesnis, nes sveikieji skaičiai nebus įtraukti į kabutes, todėl galėtumėte išsiaiškinti, kad vartotojo įvestyje yra tik skaičiai.
  • Turbūt yra kitų atvejų, apie kuriuos nežinau. Gali būti, kad tai yra naudingas šaltinis kai kurioms subtilesnėms problemoms, su kuriomis galite susidurti.
1568 m
13 сент. Atsakymą pateikė Matt Sheppard 13 rugsėjis 2008-09-13 12:48 '08, 12:48 2008-09-13 12:48

Kiekvienas atsakymas čia apima tik dalį problemos. Iš tiesų yra keturios skirtingos užklausos dalys, kurias galime dinamiškai pridėti prie jų:

  • eilutė
  • numerį
  • identifikatorius
  • sintaksinis raktinis žodis.

Ir paruošti pareiškimai apima tik du iš jų.

Bet kartais mes turime padaryti mūsų užklausą dar dinamiškesnį, pridedant ir operatorius ar identifikatorius. Taigi, mums reikia skirtingų apsaugos būdų.

Apskritai šis požiūris į apsaugą grindžiamas baltais sąrašais.

Tokiu atveju kiekvienas dinaminis parametras jūsų kode turi būti koduotas ir parenkamas iš šio rinkinio. Pavyzdžiui, norint atlikti dinaminį užsakymą:

 $orders = array("name", "price", "qty"); // Field names $key = array_search($_GET['sort'], $orders)); // See if we have such a name $orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :) $query = "SELECT * FROM 'table' ORDER BY $orderby"; // Value is safe 

Tačiau yra dar vienas būdas apsaugoti identifikatorius - patikrinimą. Tol, kol turėsite identifikatorių kabutėse, galite išvengti viduje pateikiamų citatų, padvigubindami juos.

Kaip papildomą žingsnį galime pasiskolinti tikrai puikią idėją naudoti paruoštą operatorių tam tikrą vietos žymeklį (įgaliotąjį, kuris atspindi tikrąją vertę užklausoje) ir sugalvoti kitokio tipo vietos žymeklį - vietos žymenį.

Taigi, trumpai tariant, tai yra vietovė , o ne parengta išraiška gali būti laikoma sidabro kulka.

Taigi, bendra rekomendacija gali būti suformuluota taip: tol, kol į užklausą pridėsite dinamines dalis, naudodami vietos žymenis (ir, žinoma, šie vietovės yra tvarkomi teisingai), galite būti tikri, kad jūsų prašymas yra saugus .

Tačiau yra problemų su SQL sintaksės raktiniais žodžiais (pvz., AND , DESC ir kt.), Tačiau šiuo atveju vienintelis požiūris yra baltas.

Atnaujinti

Nors visuotinai sutinkama su geriausia apsauga nuo SQL injekcijos, vis dar yra daug blogų praktikų. Kai kurie iš jų yra pernelyg giliai įsišakniję PHP vartotojų protuose. Pavyzdžiui, šiame puslapyje yra (nors ir nematoma daugeliui lankytojų) daugiau nei 80 ištrintų atsakymų - visi juos ištrina bendruomenė dėl prastos kokybės arba dėl blogų ir pasenusių praktikų propagandos. Dar blogiau, kai kurie blogi atsakymai neištrinami, bet klesti.

Pavyzdžiui, (1) yra (2) dar (3) daug (4) atsakymų (5) , įskaitant antrąjį atsakymą su didžiausiu balsų skaičiumi, suteikiant jums rankinį linijų patikrinimą - pasenusį požiūrį, kuris pasirodė esąs nesaugus.

Arba yra šiek tiek geresnis atsakymas, kuris tiesiog siūlo kitokį eilutės formatavimo būdą ir netgi gali pasigirti kaip galutinis panacėja. Nors, žinoma, tai nėra. Šis metodas nėra geresnis už įprastą styginių formatavimą, tačiau tuo pačiu metu jis išlaiko visus trūkumus: jis taikomas tik styginiams ir, kaip ir bet kuris kitas rankinis formatavimas, iš tikrųjų yra neprivaloma, neprivaloma priemonė, kuriai būdinga bet kokia žmogaus klaida.

Manau, kad visa tai yra dėl to, kad yra labai senas prietaras, kurį palaiko tokios institucijos kaip „ OWASP“ arba „PHP“ vadovas , kuriame skelbiama lygybė tarp „išėjimų“ ir apsaugos nuo SQL injekcijų.

Nepriklausomai nuo to, ką „PHP“ vadovas sako amžiams, *_escape_string jokiu būdu nepadaro duomenų saugiais ir niekada neskirta jiems. Be to, kad yra nenaudinga jokiai kitai SQL daliai, išskyrus eilutę, rankinis pabėgimas yra neteisingas, nes jis yra rankinis, o ne automatinis.

OWASP dar labiau pablogina, pabrėždama būtinybę vengti vartotojo įvesties, kuri yra visiškai nesąmonė: apsaugos nuo injekcijos kontekste neturėtų būti tokių žodžių. Kiekvienas kintamasis yra potencialiai pavojingas - nepriklausomai nuo šaltinio! Kitaip tariant, kiekvienas kintamasis turi būti teisingai suformatuotas taip, kad jį būtų galima įterpti į užklausą, nepriklausomai nuo šaltinio. Tai yra paskirties vieta. Tuo metu, kai kūrėjas pradeda atskirti avis nuo ožkų (galvodamas, ar tam tikras kintamasis yra „saugus“, ar ne), jis pirmasis žingsnis link katastrofos. Jau nekalbant apie tai, kad net formuluotė reiškia didžiulį pasitraukimą įėjimo tašką, panašią į labai stebuklingą kabučių funkciją, kuri jau buvo niekinta, pasmerkta ir ištrinta.

Taigi, skirtingai nuo „išėjimo“, paruošti pareiškimai yra priemonė, kuri tikrai apsaugo nuo SQL injekcijos (kai taikoma).

Jei vis dar nesate tikri, čia yra žingsnis po žingsnio paaiškinimas, kurį parašiau, „ kirtimo vadovas“, kaip užkirsti kelią SQL injekcijoms , kur išsamiai paaiškinau visus šiuos klausimus ir net sukaupiau skyrių, skirtą visiškai blogoms praktikoms ir jų atskleidimui.

992
24 нояб. Atsakymas suteikiamas tavo bendrovei lapkričio 24 d. 2011-11-24 12:50 '11, 12:50, 2011-11-24 12:50

Norėčiau naudoti SKVN (PHP duomenų objektus) paleisti parametruotas SQL užklausas.

Tai ne tik apsaugo nuo SQL injekcijos, bet ir pagreitina užklausas.

Naudodami SKVN, o ne mysql_ , mysqli_ ir pgsql_ , jūs iš savo duomenų bazės padarote šiek tiek abstraktesnę programą, retais atvejais, kai turite keisti duomenų bazės teikėjus.

791
13 сент. Atsakymas pateikiamas Kibbee 13 sep . 2008-09-13 03:02 '08 at 3:02 am 2008-09-13 03:02

Naudokite PDO ir parengtas užklausas.

( $conn yra PDO objektas)

 $stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)"); $stmt->bindValue(':id', $id); $stmt->bindValue(':name', $name); $stmt->execute(); 
586
13 сент. Atsakymas duotas Imran rugsėjo 13 d 2008-09-13 16:20 '08 at 4:20 pm 2008-09-13 16:20

Kaip matote, žmonės siūlo, kad jūs kuo geriau išnaudotumėte parengtus pareiškimus. Tai netiesa, bet kai jūsų užklausa vykdoma tik vieną kartą kiekvienam procesui, bus šiek tiek sumažėjęs našumas.

Aš susidūriau su šia problema, bet manau, kad ją išsprendžiau labai sudėtingai - kaip įsilaužėliai naudojasi, kad nesinaudotų kabutėmis. Aš tai panaudojau kartu su imituotais parengtais pareiškimais. Aš naudoju jį užkirsti kelią visoms galimoms SQL injekcijos atakoms.

Mano požiūris:

  • Jei tikitės, kad įvestis bus sveikasis skaičius, įsitikinkite, kad jis tikrai yra sveikas skaičius. Kalboje su kintamu tipu, pvz., PHP, tai labai svarbu. Galite naudoti, pavyzdžiui, šį labai paprastą, bet galingą sprendimą: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • Jei tikitės ko nors kito iš sveikojo skaičiaus šešių . Jei jį nuleisite, vengsite rašyti. C / C ++ sistemoje yra funkcija mysql_hex_string() , PHP galite naudoti bin2hex() .

    Nesijaudinkite, kad eilutė su patikrinta eilute bus 2 kartus didesnė už pradinį ilgį, nes net jei naudojatės mysql_real_escape_string , PHP turėtų skirti tą patį pajėgumą ((2*input_length)+1) , kuris yra tas pats.

  • Šis šešioliktainis metodas dažnai naudojamas perduodant dvejetainius duomenis, bet nematau jokios priežasties, kodėl nenaudojau visų duomenų, kad būtų išvengta SQL injekcijos atakų. Atkreipkite dėmesį, kad turite pridėti duomenis naudodami 0x arba naudoti „MySQL UNHEX funkciją.

Taigi, pavyzdžiui, užklausa:

 SELECT password FROM users WHERE name = 'root' 

Bus:

 SELECT password FROM users WHERE name = 0x726f6f74 

arba

 SELECT password FROM users WHERE name = UNHEX('726f6f74') 

Hex - puikus pabėgimas. Negalima įvesti.

Skirtumas tarp UNHEX ir 0x prefikso

Pastabose buvo diskutuojama, todėl pagaliau noriu aiškiai pasakyti. Šie du metodai yra labai panašūs, tačiau tam tikrais būdais jie šiek tiek skiriasi:

Priedas ** 0x ** gali būti naudojamas tik duomenų stulpeliuose, tokiuose kaip char, varchar, tekstas, blokas, dvejetainiai ir pan. .
Be to, jos naudojimas yra šiek tiek sudėtingas, jei ketinate įterpti tuščią eilutę. Turėsite jį visiškai pakeisti '' , arba gausite klaidos pranešimą.

UNHEX () dirba bet kuriame stulpelyje; Jums nereikia nerimauti dėl tuščios eilutės.


Hex metodai dažnai naudojami kaip išpuoliai.

Atkreipkite dėmesį, kad šis šešioliktainis metodas dažnai naudojamas kaip SQL injekcijos ataka, kur sveikieji skaičiai yra lygiai tokie patys kaip stygos ir yra pabėgę tik naudojant mysql_real_escape_string . Tada galite išvengti kabučių.

Pavyzdžiui, jei ką nors panašaus:

 "SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"]) 

ataka gali būti lengvai pasiekiama. Apsvarstykite šį kodą, grąžintą iš jūsų scenarijaus:

PASIRINKITE ... KUR id = -1 sąjunga, visi pasirinkite table_name iš information_schema.tables

ir dabar tiesiog ištraukite lentelės struktūrą:

SELECT ... WHERE id = -1 sąjunga visi pasirinkite column_name iš information_schema.column, kur table_name = 0x61727469636c65

Ir tada tiesiog pasirinkite norimus duomenis. Ar ne tai kietas?

Bet jei injekcijos vietos koduotojas yra šešioliktainis, injekcija nebus įmanoma, nes užklausa atrodys taip: SELECT ... WHERE id = UNHEX('2d312075...3635')

521
03 окт. Atsakyti Zaffy 03 okt. 2012-10-03 17:07 '12, 17:07, 2012-10-03 17:07

SVARBU

Geriausias būdas užkirsti kelią SQL įpurškimui yra naudoti parengtus pareiškimus, o ne pabėgti, kaip rodo priimtas atsakymas .

Yra bibliotekų, tokių kaip „ Aura.Sql“ ir „ EasyDB“, kurios leidžia kūrėjams lengviau naudoti parengtus pareiškimus. Norėdami sužinoti daugiau apie tai, kodėl paruošti pareiškimai yra geresni sustabdant „SQL“ injekciją , žr. Šį „ mysql_real_escape_string() ir neseniai dokumentuotus „Unicode SQL“ įvedimo pažeidžiamumus „WordPress“ .

Injekcijos prevencija - mysql_real_escape_string ()

PHP turi specialiai sukurtą funkciją, kad būtų užkirstas kelias šiems išpuoliams. Jums tereikia naudoti funkcijų funkciją, mysql_real_escape_string .

mysql_real_escape_string užima eilutę, kuri bus naudojama MySQL užklausoje ir grąžina tą pačią eilutę, kurioje visi SQL injekcijos bandymai yra saugiai ištrūkę. Iš esmės jis pakeis šias nepageidaujamas kabutes ('), kurias vartotojas gali naudoti naudodamas „MySQL“ vietos žymeklį, išsaugotą citatą.

PASTABA. Norėdami naudoti šią funkciją, turite prisijungti prie duomenų bazės!

// Prisijunkite prie „MySQL“

 $name_bad = "' OR 1'"; $name_bad = mysql_real_escape_string($name_bad); $query_bad = "SELECT * FROM customers WHERE username = '$name_bad'"; echo "Escaped Bad Injection: <br />" . $query_bad . "<br />"; $name_evil = "'; DELETE FROM customers WHERE 1 or username = '"; $name_evil = mysql_real_escape_string($name_evil); $query_evil = "SELECT * FROM customers WHERE username = '$name_evil'"; echo "Escaped Evil Injection: <br />" . $query_evil; 

Daugiau informacijos rasite „ MySQL“ - „SQL Injection Prevention“ .

470
17 июня '11 в 7:00 2011-06-17 07:00 atsakymas pateikiamas rahularyansharma birželio 17 d. 11 val

Saugos įspėjimas : šis atsakymas neatitinka saugos rekomendacijų. Pabėgimas neleidžia užkirsti kelio SQL injekcijai , vietoj to naudokite paruoštus pareiškimus. Naudokite toliau aprašytą strategiją savo pačių rizika. (Be to, mysql_real_escape_string() buvo pašalintas PHP 7.)

Pasenęs įspėjimas : šiuo metu „mysql“ plėtinys yra pasenęs. rekomenduojame naudoti mysqli plėtinį

Galite tai padaryti kažką paprasto:

 $safe_variable = mysqli_real_escape_string($_POST["user-input"]); mysqli_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')"); 

Tai neišsprendžia visų problemų, tačiau tai yra labai geras žingsnis. Я упустил очевидные элементы, такие как проверка существования переменной, формат (цифры, буквы и т.д.).

429
ответ дан Tanerax 13 сент. '08 в 3:15 2008-09-13 03:15

Независимо от того, что вы делаете в конечном итоге, убедитесь, что вы проверяете, что ваш вход еще не был искажен magic_quotes или каким-либо другим хорошо продуманным мусором, и, если необходимо, запустите его через stripslashes или что-то еще для его дезинфекции.

362
ответ дан Rob 08 дек. '08 в 8:26 2008-12-08 08:26

Параметрированный запрос И проверка ввода - это путь. Существует множество сценариев, в которых может произойти SQL-инъекция, даже если используется mysql_real_escape_string() .