Și cel mai mic ajutor ar fi din funcția pe care ați dezvoltat-o. PHP: \"Citate\". Scrierea de interogări mysql, bare oblice, ghilimele de evadare Căutare în cod

șir de ghilimele (11)

Încerc să aflu cea mai bună modalitate de a scrie interogări. Înțeleg și importanța de a fi consecvenți. Până acum am folosit aleatoriu ghilimele simple, ghilimele duble și backtick-urile fără niciun gând real.

$interogare = "INSERT INTO table (id, col1, col2) VALUES (NULL, val1, val2)";

De asemenea, în exemplul de mai sus, luați în considerare că „table”, „col[n]” și „val[n]” pot fi variabile.

Care este standardul pentru asta? Ce faci?

Am citit răspunsuri la întrebări similare de aproximativ 20 de minute, dar nu pare să existe un răspuns definitiv la această întrebare.

Răspunsuri

Acum să presupunem că utilizați variabila direct post în interogarea MySQL, apoi utilizați-o astfel:

$query = "INSERT INTO `table` (`id`, `name`, `email`) VALUES (" ".$_POST["id"]." ", " ".$_POST["nume"]." ", " ".$_POST["e-mail"].." ")";

Aceasta este cea mai bună practică pentru utilizarea variabilelor PHP în MySQL.

În principal în Mysql, aceste tipuri de identificatori sunt utilizate în interogările ` , " , " și ().

    „ sau „ folosiți pentru a include un șir ca valoare „26/01/2014 00:00:00” sau „26/01/2014 00:00:00” . Acest identificator este utilizat numai pentru funcția șir „01/26/2014 00:00:00”, cum ar fi now() sau sum ,max .

    ` utilizați pentru a include un tabel sau un tabel de tabel, de exemplu, selectați nume_coloană din nume_tabel unde id = "2"

    () sunt folosite doar pentru a include pur și simplu părți ale unei interogări, de exemplu, selectați column_name din table_name unde (id = "2" și gen = "masculin") sau name = "rakesh.

În afară de toate răspunsurile (bine explicate), nu au fost menționate mai jos și vin la aceste întrebări și răspunsuri des.

Pe scurt; MySQL crede că vrei să faci matematică pe propriul tabel/coloană și interpretați cratime precum „e-mail” ca e-mail.

Negarea răspunderii. Așa că m-am gândit să adaug asta ca răspuns „FYI” pentru cei care sunt complet noi în lucrul cu bazele de date și care ar putea să nu înțeleagă termenii tehnici deja descriși.

(Există răspunsuri bune mai sus cu privire la natura SQL a întrebării dvs., dar acest lucru poate fi relevant și dacă sunteți nou în PHP.)

Poate fi important să rețineți că PHP tratează diferit ghilimelele simple și duble...

Șirurile cu ghilimele simple sunt „literale” și sunt destul de multe șiruri WYSIWYG. Șirurile dublu ghilimele sunt interpretate de PHP pentru o posibilă înlocuire a variabilelor (backreferences în PHP nu sunt exact șiruri, ele execută comanda în shell și returnează rezultatul).

$foo = "bar"; echo "există un $foo"; // Există un ecou $foo "există un $foo"; // Există un ecou bar `ls -l`; // ... o listă de directoare

Dacă tabelele și valorile cols sunt variabile, atunci există două moduri:

Cu ghilimele duble „” interogarea completă este:

$interogare = "INSERT INTO $nume_tabel (id, $col1, $col2) VALORI (NULL, "$val1", "$val2"");

$interogare = „INSERT INTO „.$table_name.” (id, „.$col1.”, „.$col2.”) VALORI (NULL, „”.$val1.”, „”.$val2.””) ";

Cu ghilimele simple „” :

$interogare = "INSERT INTO ".$table_name." (id, ".$col1.", ".$col2.") VALORI (NULL, ".$val1.", ".$val2."");

Utilizați backticks `` când numele coloanei/valorii este similar cu un cuvânt cheie rezervat MySQL.

Notă. Dacă specificați un nume de coloană cu un nume de tabel, utilizați backtick-uri ca acesta:

`nume_tabel` . `nume_coloană`<- Примечание: исключить. из задних клещей.

Backtick-urile ar trebui folosite pentru identificatorii de tabel și coloană, dar sunt necesare numai atunci când identificatorul este un cuvânt cheie rezervat MySQL sau când identificatorul conține caractere de spațiu sau caractere în afara setului limitat (vezi mai jos). Este adesea recomandat să evitați utilizarea cuvintelor cheie rezervate ca identificatori de coloană sau tabel, dacă este posibil, pentru a evita problema citațiilor.

Ghilimele simple trebuie folosite pentru valorile șirului, cum ar fi în lista VALUES(). Ghilimelele duble sunt acceptate de MySQL și pentru valorile șirului, dar ghilimelele simple sunt mai larg acceptate de alte RDBMS-uri, așa că este o idee bună să folosiți ghilimele simple în loc de ghilimele duble.

MySQL se așteaptă, de asemenea, ca valorile literale DATE și DATETIME să fie ghilimele simple ca șiruri, cum ar fi „2001-01-01 00:00:00” . Pentru mai multe informații, consultați documentația de literatură Data și ora, în special alternative la utilizarea cratimei - ca separator de segment în șirurile de date.

Deci, folosind exemplul dvs., aș arunca dublu șirul PHP și aș folosi ghilimele simple pentru valorile „val1”, „val2” . NULL este un cuvânt cheie MySQL și o non-valoare și, prin urmare, nu este utilizat.

Niciunul dintre acești identificatori de tabel sau de coloană nu sunt cuvinte rezervate sau nu folosesc caractere care necesită ghilimele, dar oricum le-am citat cu înapoi (mai multe despre asta mai târziu...).

Funcțiile legate de RDBMS (cum ar fi NOW() în MySQL) nu trebuie citate, deși argumentele lor sunt supuse acelorași reguli sau reguli de citare deja menționate.

Backtick (`) tabel și coloană ┬──── ┬──┬───────┐ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ = ↓ ↓ ↓ ↓ INSERT INTO `table` (`id`, `col1`, `col2`, `date`, `updated`) VALORI (NULL, "val1", "val2", "2001-01-01", NOW())„; Cuvânt cheie fără ghilimele ─────┴┴┴┘ │ │ │ │ │ │ │││││ Șiruri de caractere cu ghilimele simple (”) ────────└─── ───┴── ┴────┘ │ │ │││││ ghilimele simple ("") DATA ───────────────────────────── ───┴── Funcție necotată ───────────────────────── ────────────────── ──┴┴┴┴┘

Interpolare variabilă

Modelele de ghilimele pentru variabile nu se schimbă, deși dacă intenționați să interpolați variabile direct într-un șir, acesta trebuie să fie ghilimele duble în PHP. Doar asigurați-vă că scăpați corect de variabilele pentru utilizare în SQL. (Este recomandat să utilizați un API care acceptă instrucțiuni pregătite ca o apărare împotriva injectării SQL.)

// Același lucru cu unele înlocuiri de variabile // Aici, un nume de tabel variabil $table este ghilimele înapoi, iar variabilele // din lista VALORI sunt ghilimele simple $query = "INSERT INTO `$masă`(`id`, `col1`, `col2`, `data`) VALORI (NULL, „$val1”, „$val2”, „$date”)";

Declarații pregătite

Când lucrați cu declarații pregătite, consultați documentația pentru a determina dacă elementele de completare a declarațiilor trebuie incluse. Cele mai populare API-uri disponibile în PHP, PDO și MySQLi implică neautorizat substituenți, cum ar fi majoritatea API-urilor de instrucțiuni pregătite în alte limbi:

// Exemplu PDO cu parametri numiți, $interogare fără ghilimele = "INSERT INTO `table` (`id`, `col1`, `col2`, `date`) VALORI (:id, :col1, :col2, :date)" ; // Exemplu MySQLi cu ? parametri, $interogare fără ghilimele = "INSERT INTO `table` (`id`, `col1`, `col2`, `date`) VALORI (?, ?, ?, ?)";

Simboluri care returnează o referință înapoi în identificatori:

De exemplu:

Același lucru se poate face pentru numele tabelelor și numele câmpurilor. Aceasta este foarte obicei bun dacă legați id-ul bazei de date cu ferestrele din spate.

Consultați acest răspuns pentru a afla mai multe despre inferențe inverse.

Acum despre ghilimele duble și ghilimele simple (Michael a menționat deja acest lucru).

Dar pentru a defini valoarea, trebuie să folosiți ghilimele simple sau duble. Să vedem un alt exemplu.

INSERT INTO `tablename` (`id, `title`) VALORI (NULL, title1);

Aici am uitat în mod deliberat să închej titlul1 între ghilimele. Serverul va accepta acum titlul1 ca nume de coloană (adică identificator). Deci, pentru a indica că aceasta este o valoare, trebuie să utilizați ghilimele duble sau simple.

INSERT INTO `tablename` (`id, `title`) VALORI (NULL, "title1");

Acum, combinate cu PHP, ghilimelele duble și ghilimele simple fac scrierea interogărilor mult mai ușoară. Să ne uităm la versiunea modificată a interogării din întrebarea dvs.

$interogare = "INSERT INTO `table` (`id`, `col1`, `col2`) VALORI (NULL, "$val1", "$val2"");

Acum, folosind ghilimele duble în PHP, veți face ca variabilele $val1 și $val2 să-și folosească valorile, creând astfel o interogare validă. ca

$val1 = "valoarea mea 1"; $val2 = "valoarea mea 2"; $interogare = "INSERT INTO `table` (`id`, `col1`, `col2`) VALORI (NULL, "$val1", "$val2"");

INSERT INTO `table` (`id`, `col1`, `col2`) VALORI (NULL, "valoarea mea 1", "valoarea mea 2")

Au fost multe răspunsuri utile aici, culminând în general cu două puncte.

  1. BACKTICK-urile (`) sunt folosite în jurul numelor de identificare.
  2. CIGILE SINGLE ("") sunt folosite în jurul valorilor.

Și așa cum a spus @MichaelBerkowski

Backtick-urile ar trebui folosite pentru identificatorii de tabel și coloană, dar sunt necesare numai atunci când identificatorul este un cuvânt cheie rezervat MySQL sau când identificatorul conține caractere de spațiu sau caractere în afara setului limitat (vezi mai jos). Este adesea recomandat să evitați utilizarea cuvintelor cheie rezervate ca identificatori de coloană sau tabel, dacă este posibil, pentru a evita problema citațiilor.

Există un caz în care identificatorul nu poate fi cuvânt cheie rezervat sau conţin caractere cu spații albe sau caractere din afara setului limitat dar cu siguranță necesită backlink-uri în jurul lor.

123E10 este un nume de identificator valid, dar și un literal INTEGER valid.

[Fără a intra în detalii despre cum ați obține un nume de ID ca acesta] Să presupunem că vreau să creez un tabel temporar numit 123456e6.

Fără EROARE la backticks.

DB > creează tabelul temporar `123456e6` (`id` char (8)); Interogare OK, 0 rânduri afectate (0,03 sec)

EROARE dacă nu utilizați apeluri inverse.

DB > creați tabelul temporar 123451e6 (`id` char (8)); EROARE 1064 (42000): Aveți o eroare în sintaxa dvs. SQL; verificați manualul care corespunde versiunii dvs. de server MariaDB pentru sintaxa corectă de utilizat lângă „123451e6 (`id` char (8))” la linia 1

Cu toate acestea, 123451a6 este un nume de ID fin (fără backtick-uri).

DB > creați tabelul temporar 123451a6 (`id` char (8)); Interogare OK, 0 rânduri afectate (0,03 sec)

Acest lucru se datorează în totalitate faptului că 1234156e6 este, de asemenea, un număr exponențial.

Ghilimele simple trebuie folosite pentru valorile șirului, cum ar fi în lista VALUES().

Backtick-urile sunt de obicei folosite pentru a indica un identificator și pot fi, de asemenea, sigure datorită utilizării ocazionale a cuvintelor cheie rezervate.

Atunci când sunt combinate cu PHP și MySQL, ghilimelele duble și ghilimele simple simplifică foarte mult timpul de scriere a interogărilor.

Există două tipuri de citate în MySQL:

  1. " pentru a include literali șir
  2. ` pentru a include identificatori precum numele tabelelor și coloanelor

Și apoi mai este „acesta este un caz special. Poate fi folosit pentru unu dintre obiectivele de mai sus la un moment dat, în funcție de sql_mode al serverului:

  1. Mod implicit„caracter” poate fi folosit pentru a imbrica literale de șir „
  2. În modul ANSI_QUOTES „ simbolul poate fi folosit pentru a include identificatori, ANSI_QUOTES

Următoarea interogare va produce rezultate (sau erori) diferite, în funcție de modul SQL:

SELECTAȚI „coloana” DIN tabelul WHERE foo = „bară”

ANSI_QUOTES dezactivat

Interogarea va selecta șirul literal „coloană” unde coloana foo este egală cu șirul „bară”

Activat ANSI_QUOTES

Interogarea va selecta coloana în care coloana foo este egală cu coloana

Când să utilizați

  • Vă sugerez să evitați să utilizați „, astfel încât codul dvs. să nu depindă de modurile SQL
  • Includeți întotdeauna identificatori, deoarece aceasta este o bună practică (destul de multe întrebări despre SO discută acest lucru)

Există o diferență clară între utilizarea lui " " și " " .

Când „ ” este folosit pe tot parcursul, nu există nicio „transformare sau traducere”. Este imprimat ca atare.

Cu „ ”, orice înconjoară este „tradus sau transformat” în valoarea sa.

Ceea ce vreau să spun prin traducere/conversie este aceasta: orice conținut între ghilimele simple nu va fi „tradus” la valorile lor. Ele vor fi acceptate pentru că sunt între ghilimele. Exemplu: a=23 , apoi ecoul „$a” va genera $a la ieșirea standard. În timp ce ecoul „$a” va produce 23 la ieșirea standard.

(PHP 4 >= 4.3.0, PHP 5)

mysql_real_escape_string — Escape caractere speciale dintr-un șir pentru a fi utilizate într-o instrucțiune SQL

Descriere

mysql_real_escape_string (șir $unescaped_string [, resursa $link_identifier = NULL]): șir

Escape caractere speciale din unscaped_string , ținând cont de setul de caractere curent al conexiunii, astfel încât să fie sigur să îl plasați într-un mysql_query(). Dacă urmează să fie introduse date binare, această funcție trebuie utilizată.

mysql_real_escape_string() apelează funcția de bibliotecă MySQL mysql_real_escape_string, care adaugă barele oblice inverse la următoarele caractere: \x00, \n, \r, \ , " , " și \x1a.

Această funcție trebuie întotdeauna utilizată (cu puține excepții) pentru a asigura siguranța datelor înainte de a trimite o interogare către MySQL.

Prudență

Securitate: setul de caractere implicit

Setul de caractere trebuie setat fie la nivel de server, fie cu funcția API mysql_set_charset() pentru ca acesta să afecteze mysql_real_escape_string() . Consultați secțiunea de concepte despre seturile de caractere pentru mai multe informații.

Parametrii

unscaped_string

Șirul care urmează să fie scăpat.

Link_identifier

Conexiunea MySQL. Dacă identificatorul linkului nu este specificat, ultimul link deschis de mysql_connect() este asumat. Dacă nu se găsește un astfel de link, va încerca să creeze unul ca și cum mysql_connect() fusese sunat fără argumente. Dacă nu este găsită sau stabilită nicio conexiune, an E_AVERTISMENT eroare de nivel este generată.

Valori returnate

Returnează șirul escape sau FALS pe eroare.

Erori/Excepții

Executarea acestei funcții fără o conexiune MySQL prezentă va emite și E_AVERTISMENT erori de nivel PHP. Executați această funcție numai cu o conexiune MySQL validă prezentă.

Exemple

Exemplul #1 Simplu mysql_real_escape_string() exemplu

//Conectați
$link = mysql_connect("mysql_host" , "mysql_user" , "mysql_parola" )
SAU die(mysql_error());

//Interogare
$interogare = sprintf ( „SELECT * FROM users WHERE user="%s" AND password="%s"",
mysql_real_escape_string($utilizator),
mysql_real_escape_string($parolă));
?>

Exemplul #2 mysql_real_escape_string() necesită un exemplu de conexiune

Acest exemplu demonstrează ce se întâmplă dacă o conexiune MySQL nu este prezentă la apelarea acestei funcție.

Exemplul de mai sus va scoate ceva similar cu:

Avertisment: mysql_real_escape_string(): Nu există un astfel de fișier sau director în /this/test/script.php pe linia 5 Avertisment: mysql_real_escape_string(): O legătură către server nu a putut fi stabilită în /this/test/script.php pe linia 5 bool(false) string(41) "SELECT * FROM actori WHERE nume_de_nume = """

Exemplul #3 Un exemplu de atac cu injecție SQL

// Nu am verificat $_POST["parola"], ar putea fi orice ar fi dorit utilizatorul! De exemplu:
$_POST [ "nume utilizator" ] = "aidan" ;
$_POST [ "parola" ] = "" SAU ""="" ;

// Interogați baza de date pentru a verifica dacă există utilizatori care se potrivesc
$interogare = ( $_POST [ "nume utilizator" ]) " ȘI parola = " ( $_POST [ "parolă" ]) "" ;
mysql_query($interogare);

// Aceasta înseamnă că interogarea trimisă către MySQL ar fi:
echo $interogare ;
?>

Interogarea trimisă către MySQL:

Acest lucru ar permite oricui să se conecteze fără o parolă validă.

Note

Este necesară o conexiune MySQL înainte de utilizare mysql_real_escape_string() altfel o eroare de nivel E_AVERTISMENT este generată și FALS este returnat. Dacă link_identifier nu este definit, se utilizează ultima conexiune MySQL.

Notă: mysql_real_escape_string() nu scapă % și _ . Acestea sunt metacaractere în MySQL dacă sunt combinate cu CA, ACORDA, sau REVOCA.

acum 8 ani

Doar o mică funcție care imită mysql_real_escape_string original, dar care nu are nevoie de o conexiune mysql activă.Ar putea fi implementată ca o funcție statică într-o clasă de bază de date.Sper că ajută pe cineva.

funcția mysql_escape_mimic ($inp) (
if(este_matrice($inp))
returnează hartă_matrice (__METODA__ , $inp );

If(!empty($inp ) && is_string ($inp )) (
returnează str_replace (matrice("\\" , "\0" , "\n" , "\r" , """ , """ , "\x1a" ), matrice("\\\\" , "\ \0" , "\\n" , "\\r" , "\\"" , "\\"", "\\Z" ), $inp );
}

Returnează $inp ;
}
?>

acum 13 ani

Rețineți că mysql_real_escape_string nu pune înainte barele oblice inverse \x00, \n, \r și \x1a așa cum este menționat în documentație, dar înlocuiește de fapt caracterul cu o reprezentare MySQL acceptabilă pentru interogări (de exemplu, \n este înlocuit cu „\ n" literal). (\, " și " sunt scapat așa cum este documentat) Acest lucru nu schimbă modul în care ar trebui să utilizați această funcție, dar cred că este bine de știut.

acum 6 ani

Nicio discuție despre evadare nu este completă fără a spune tuturor că practic nu ar trebui să utilizați niciodată intrare externă pentru a genera cod interpretat. Acest lucru este valabil pentru instrucțiunile SQL sau orice ați numi orice fel de funcție „eval”.

Deci, în loc să utilizați această funcție teribil de deteriorată, utilizați instrucțiunile pregătite parametrice.

Sincer, utilizarea datelor furnizate de utilizator pentru a alcătui declarații SQL ar trebui considerată neglijență profesională și ar trebui să fii tras la răspundere de către angajator sau client pentru că nu folosești declarații pregătite cu parametri.

Ce înseamnă asta?

Înseamnă, în loc să construiți o instrucțiune SQL ca aceasta:

„INSERT INTO X (A) VALUES(".$_POST["a"].)"

Ar trebui să utilizați funcția mysqli prepare() () pentru a executa o instrucțiune care arată astfel:

„INSERT ÎN X (A) VALORI(?)”

NB: Acest lucru nu înseamnă că nu ar trebui să generați niciodată instrucțiuni SQL dinamice. Ceea ce înseamnă este că nu trebuie să utilizați niciodată date furnizate de utilizator pentru a genera acele instrucțiuni. Orice date furnizate de utilizator ar trebui să fie transmise ca parametri în instrucțiune după ce a fost fost pregătit.

Deci, de exemplu, dacă construiți un mic cadru și doriți să faceți o inserare într-un tabel pe baza URI-ului solicitării, este în interesul dvs. să nu luați valoarea $_SERVER["REQUEST_URI"] (sau orice parte din acesta) și concatenați-o direct cu interogarea dvs. În schimb, ar trebui să analizați porțiunea valorii $_SERVER["REQUEST_URI"] pe care o doriți și să o mapați printr-un fel de funcție sau matrice asociativă unui non-utilizator. valoarea furnizată. Dacă maparea nu produce nicio valoare, știți că ceva este în neregulă cu datele furnizate de utilizator.

Nerespectarea acestui lucru a fost cauza unui număr de probleme de injecție SQL în cadrul Ruby On Rails, chiar dacă folosește instrucțiuni pregătite parametrice. Așa a fost piratat GitHub la un moment dat. Deci, nicio limbă nu este imună la această problemă. De aceea aceasta este o bună practică generală și nu ceva specific PHP și de ce ar trebui să o adoptați cu adevărat.

De asemenea, ar trebui să faceți în continuare un fel de validare a datelor furnizate de utilizatori, chiar și atunci când utilizați declarații pregătite parametrice. Acest lucru se datorează faptului că datele furnizate de utilizator vor deveni adesea parte din HTML generat și doriți să vă asigurați că datele furnizate de utilizator nu vor cauza probleme de securitate în browser.

acum 9 ani

Există o ciudație interesantă în exemplul #2 despre injectarea SQL: AND are prioritate față de SAU, deci interogarea injectată se execută de fapt ca WHERE (user="aidan" AND password="") SAU ""="", deci în schimb de a returna o înregistrare a bazei de date corespunzătoare unui nume de utilizator arbitrar (în acest caz „aidan”), ar returna de fapt TOATE înregistrările bazei de date. În nicio ordine anume. Deci un atacator s-ar putea conecta cu orice cont, dar nu neapărat cu orice cont. control asupra ce cont este.

Desigur, un potențial de atac și-ar putea modifica pur și simplu parametrii pentru a viza utilizatorii de interes specifici:

//De exemplu. valorile atacatorului
$_POST [ "nume utilizator" ] = "" ;
$_POST[„parolă”] = "" SAU utilizator = "administrator" ȘI "" = "";

// Interogare incorectă
$interogare = „SELECT * FROM users WHERE user="$_POST [ nume de utilizator ] " AND parola=" $_POST [ parola ] "" ;

echo $interogare ;

// Interogarea trimisă către MySQL ar citi:
// SELECTAȚI * FROM utilizatorii WHERE user="" AND password="" OR user="administrator" AND ""="";
// care ar permite oricui să obțină acces la contul numit „administrator”

?>

1 an in urma

@feedr
I-am elaborat nota după cum urmează:
$string = "asda\0sd\x1aas\\\\\\\\dasd\"asdasd\na\"\"sdasdad";
$array1 = array("\\\\\\\\", "\0", "\n", "\r", """, """, "\x1a");
$matrice2 = matrice("\\\\\\\\\\\\\\\\", "\\\0", "\\\n", "\\\r", "\\\ " ", "\\\"", "\\\Z");
echo($șir);
echo(PHP_EOL);
pentru($i=0; $i dacă ($i==0)
$p = "/(?altfel
$p = "/(?echo($i);
echo($p);
echo($array2[$i]);
$șir = preg_replace($p, $array2[$i], $șir);
echo("\t");
echo($șir);
echo(PHP_EOL);
}
echo(PHP_EOL);
echo($șir);

acum 2 ani

Pentru a-l cita pe Sam la Numb Safari

[ „Nici o discuție despre evadare nu este completă fără a spune tuturor că, practic, nu ar trebui să utilizați niciodată intrare externă pentru a genera cod interpretat. Acest lucru este valabil pentru instrucțiunile SQL sau orice ați numi orice fel de funcție „eval”.

Deci, în loc să utilizați această funcție teribil de deteriorată, utilizați instrucțiunile pregătite parametrice.

Sincer, folosirea datelor furnizate de utilizator pentru a compune instrucțiuni SQL ar trebui considerată neglijență profesională și ar trebui să fii tras la răspundere de către angajator sau client pentru că nu ai folosit declarații pregătite cu parametri." ]

Sam are dreptate........

Cu toate acestea, nu cred că este logic să opriți toate igienizarea și pur și simplu să treceți sarcina asupra declarațiilor pregătite parametrice.

Un anumit dezvoltator care lucrează într-o anumită situație va ști întotdeauna mai multe despre input valid (specific acelui context).

Dacă cereți unui utilizator să treacă o valoare pe care i-ați dat-o deja și știți că toate astfel de valori încep AB****** și șirul ar trebui să aibă lungimea 7 sau 11, dar niciodată altă lungime, atunci aveți baza unui dezinfectant bun - lungimi diferite permise ale unui șir ar putea indica date vechi.

Niciodată nu aș dori să trec pur și simplu gunoiul pe care un utilizator rău intenționat le-a transmis printr-un formular către instrucțiunile pregătite parametrice, aș dori întotdeauna să-mi fac propriile verificări de sănătate și, în unele cazuri, acestea pot greși din partea precauției și alegeți pur și simplu să anulați complet operațiunea bazei de date.

În acest fel, DB-ul meu nu se înfundă cu declarații nesigure făcute în siguranță - pur și simplu nu se înfundă, ceea ce este mai bine.

Securitate în straturi - igienizarea și validarea ar trebui să fie luate în considerare în orice situație ÎNAINTE de a utiliza declarațiile pregătite.

În plus, din câte pot citi în documentul oficial
==============================================

„Escape și injecție SQL

Variabilele legate sunt trimise la server separat de interogare și, prin urmare, nu pot interfera cu aceasta. Serverul folosește aceste valori direct în punctul de execuție, după ce șablonul de instrucțiune este analizat. Parametrii legați nu trebuie să fie evadați, deoarece nu sunt niciodată înlocuiți direct în șirul de interogare"

Asta îmi sugerează că pericolul este evitat în interior prin manipulare alternativă, nu prin anulare.

Aceasta înseamnă că un proiect mare cu conversie incompletă în declarații pregătite, cod moștenit în diferite părți ale unei organizații sau servere care vorbesc între ele ar putea transmite toate veștile proaste dintr-o locație sau situație imună la una care nu este imună.

Atâta timp cât igienizarea este efectuată în mod competent, fără a suporta riscuri suplimentare, atunci personal aș rămâne cu anumite straturi de igienizare și apoi aș apela declarațiile pregătite.


În primul rând, puțin despre motivul pentru care aceste bare oblice sunt necesare în general.
Dacă substituim orice date într-o interogare, atunci pentru a distinge aceste date de comenzile SQL, acestea trebuie să fie plasate între ghilimele.
De exemplu, dacă scrii
SELECT * FROM table WHERE nume = Bill
atunci baza de date va decide că Bill este numele altui câmp, nu îl va găsi și va arunca o eroare. Prin urmare, datele înlocuite (în acest caz, numele Bill) trebuie incluse între ghilimele - atunci baza de date le va considera un șir, a cărui valoare trebuie să fie atribuită câmpului de nume:
SELECT * FROM table WHERE nume = "Bill"
Cu toate acestea, ghilimele pot apărea și în datele în sine. De exemplu,
SELECT * FROM table WHERE nume = "D"Artagnan"
Aici baza de date va decide că „D” este date, iar Artagnan este o comandă pe care nu o cunoaște și, de asemenea, va arunca o eroare. Prin urmare, este necesar să urmăriți toate datele pentru a explica bazei de date că ghilimelele (și alte caractere speciale) găsite în ele se referă la date.
Ca urmare, vom primi o solicitare corectă care nu va cauza erori:
SELECT * FROM table WHERE nume = "D\"Artagnan"

Astfel, am aflat că atunci când înlocuim datele șir într-o interogare, trebuie respectate două reguli:
- toate datele șiruri introduse trebuie să fie cuprinse între ghilimele (single sau duble, dar cele simple sunt mai convenabile și mai des folosite).
- caracterele speciale trebuie să fie eliminate cu bare oblice.

Trebuie remarcat în mod special: barele oblice adăugate NU intră în baza de date. Sunt necesare doar în cerere. Când se lovește baza, tăieturile sunt aruncate. În consecință, o greșeală obișnuită este să folosiți bare oblice atunci când preluați date din baza de date.

Toate cele de mai sus se aplică datelor și datelor șirurilor. Numerele pot fi introduse fără urmă sau înconjurați cu ghilimele. Dacă faci asta atunci NECESAR! forțați datele la tipul dorit înainte de a le introduce în interogare, de exemplu:
$id = intval ($id);
Cu toate acestea, pentru simplitate (și fiabilitate), puteți lucra cu numere ca și cu șiruri (deoarece mysql le convertește în continuare la tipul dorit). În consecință, vom urmări orice date introduse în cerere și le vom include între ghilimele.

De asemenea, mai există o regulă - opțională, dar ar trebui urmată pentru a evita erori:
Numele câmpurilor și tabelelor ar trebui să fie cuprinse între ghilimele simple din spate - „`” (tasta cu acest simbol este situată pe o tastatură standard în stânga tastei „1”). La urma urmei, numele câmpului poate coincide cu mysql cuvinte cheie, dar dacă folosim un citat din spate, atunci MySQL va înțelege că totul este corect:
SELECTAȚI * FROM `table` WHERE `data` = "2006-04-04"
Ar trebui să distingeți între aceste ghilimele și să nu le confundați unul cu celălalt. De asemenea, ar trebui să vă amintiți că backticks nu sunt scăpate de tăieturi.

Deci, am învățat cum să înlocuim corect datele într-o solicitare.
DAR! Construcția dinamică a interogărilor nu se limitează la înlocuirea datelor. Adesea trebuie să înlocuim comenzile SQL și numele câmpurilor într-o interogare. Și aici trecem la subiectul securității:

SQL Injection este o metodă de atac al hackerilor atunci când datele transferate într-un script sunt modificate în așa fel încât interogarea generată în acest script începe să efectueze ceva complet diferit de ceea ce a fost destinat.
Regulile de protecție împotriva unor astfel de atacuri pot fi împărțite în două puncte:
1. Lucrul cu date.
2. Lucrul cu controalele de interogare.

Am discutat primul punct în detaliu mai sus. Se poate spune că nu este, de fapt, o apărare. Respectarea regulilor de adăugare a datelor la o interogare este dictată, în primul rând, de cerințele SQL SYNTAX. Și ca efect secundar, avem și protecție împotriva hackingului.

Al doilea punct este mult mai dificil, deoarece nu există o singură regulă universală în ceea ce privește datele - un backtick nu va proteja numele câmpului de a fi modificat de către un hacker. Nu este posibil să folosiți ghilimele pentru a proteja numele tabelelor, instrucțiunile SQL, parametrii de comandă LIMIT și alte instrucțiuni.
Prin urmare, regula de bază la înlocuirea elementelor de control într-o interogare este:
Dacă trebuie să inserați dinamic instrucțiuni SQL sau nume de câmpuri, baze de date, tabele într-o interogare, atunci în niciun caz nu trebuie să le inserați direct în interogare.
Toate opțiunile pentru astfel de completări trebuie să fie scrise în AVANS în scriptul dvs. și selectate în funcție de ceea ce a introdus utilizatorul.
De exemplu, dacă trebuie să transmiteți un nume de câmp la comanda de către operator, atunci în niciun caz nu trebuie să îl înlocuiți direct. Trebuie să-l verificăm mai întâi. De exemplu, creați o matrice de valori valide și înlocuiți-o în cerere numai dacă parametrul transmis este prezent în această matrice:
$comenzi =array("nume" , "pret" , "cantitate" );
$key = array_search($_GET["sortare"], $comenzi));
$comandă = $comenzi [ $cheie ];
$interogare = „SELECT * FROM `table` ORDER BY$comandă" ;

Căutăm în matricea de opțiuni pre-descrise cuvântul introdus de utilizator, iar dacă îl găsim, selectăm elementul corespunzător al matricei. Dacă nu se găsește nicio potrivire, va fi selectat primul element al matricei.
Astfel, ceea ce este substituit în cerere nu este ceea ce a introdus utilizatorul, ci ceea ce a fost scris în scriptul nostru.
Același lucru trebuie făcut în toate celelalte cazuri.
De exemplu, dacă clauza WHERE este generată dinamic:
if (!empty($_GET [ "preț" ])) $where .= "preț="" . mysql_real_escape_string ($_GET [ "preț" ]). """ ;
$interogare = "SELECT * FROM `table` WHERE $unde " ;

Îmi este greu să-mi imaginez un caz în care un nume de tabel poate fi inserat dinamic într-o interogare, dar dacă se întâmplă acest lucru, atunci și numele trebuie să fie inserat doar dintr-un set predefinit în script.
Parametrii operatorului LIMIT trebuie forțați la un tip întreg folosind operații aritmetice sau funcția intval().
Nu credeți că exemplele enumerate aici epuizează toate opțiunile pentru construirea dinamică a interogărilor. Trebuie doar să înțelegeți principiul și să îl aplicați în toate astfel de cazuri.

Datorită naturii muncii mele, trebuie să efectuez audituri de securitate ale codului sursă al aplicațiilor web.
O mulțime de aplicații web și mult cod...

Nu este un secret pentru nimeni că vulnerabilitățile de injectare SQL sunt cele mai comune dintre toate vulnerabilitățile aplicațiilor web de pe partea de server. Există platforme și cadre în care astfel de lucruri sunt aproape complet excluse, de exemplu ORM și așa mai departe. Dar statisticile ne vorbesc în mod persistent despre predominanța absolută a aplicațiilor web cu interogări SQL simple concatenate pe Internet. În plus, există cazuri în care ORM este aplicabil general Nu poate, de exemplu, atunci când nu numai parametrii expresiilor, ci și logica interogării în sine la nivel de operator trebuie să depindă de datele utilizatorului.

Deci, să începem.

Personaj inutil scăpare
Găsit în 83% dintre aplicațiile web PHP vulnerabile la injecții SQL
Utilizarea funcției de evacuare pentru caractere precum
mysql_escape_string
mysql_real_escape_string
addslashes
fără ghilimele. Cel mai adesea se manifestă în parametri numerici (toate tipurile de *_id).
Exemplu
$sql = "SELECTARE utilizator din lista de utilizatori WHERE userid=".mysql_real_escape_string($_GET["uid"]);

Pare a fi un cod sigur, dar numai la suprafață. Cel mai comun model de injecții SQL în PHP din practica mea sa strecurat aici. Pentru a ataca această vulnerabilitate, un atacator trebuie pur și simplu să evite utilizarea caracterelor " " \x00 \r \n \x1a din vectorul de atac.
De exemplu:
/index.php?uid=-777 UNION SELECT password FROM userlist

Cauta in cod
Complicată de semantica limbajului. Pentru o căutare simplă puteți folosi egrep:
egrep -Rin „(selectați|actualizați|inserați|ștergeți|înlocuiți).*(din|set|în).*(mysql_escape_string|mysql_real_escape_string|adaugă bare oblice)” . | grep -v "[\""]["\"]"

Logica expresiei de căutare este următoarea: găsiți toate liniile în care nu există o secvență de ghilimele ("", "", "", "") în stânga funcțiilor de filtrare. Metoda, desigur, este departe de 100%, dar este imposibil să se solicite o expresie regulată pentru a efectua o analiză semantică.
Pentru a facilita afișarea informațiilor, puteți evidenția funcția colorată în consolă:
egrep -Rin „(selectați|actualizați|inserați|ștergeți|înlocuiți).*(din|set|în).*(mysql_escape_string|mysql_real_escape_string|adaugă bare oblice)” . | grep -v "[\""]["\"]" | egrep --color "(mysql_escape_string|mysql_real_escape_string|adaugă bare oblice)"

Pentru a vă proteja împotriva acestei vulnerabilități wildcard, cel mai bine este să utilizați tipul de turnare.
Acesta funcționează întotdeauna mai rapid și este mai fiabil decât toate tipurile de filtrare și screening.
Pentru exemplul de mai sus, patch-ul ar putea fi astfel:
$sql = "SELECTARE utilizator din lista de utilizatori WHERE userid=".intval($_GET["uid"]);

Aceasta se încheie scurtul eseu. Îi îndemn pe toți dezvoltatorii web să încerce să-și verifice sursele pentru astfel de modele. Mai bine, extindeți scriptul de căutare dat pentru oameni.

Așa că, practic, am săpat adânc în domeniile MySQL și PHP... în special măsurile de securitate pe care ar trebui să le iau atunci când mă ocup de bazele de date și intrările de formulare. Până acum am considerat că următoarele sunt foarte recomandate:

  1. Declarații pregătite
  2. Folosind _real_escape_string()
  3. NU folosește ghilimele magice, deoarece confundă bazele de date și ajunge să-ți ofere lucruri de genul „Nu l-ai numit...”.

Toate acestea sunt grozave și sunt cu ochii pe el. Cu toate acestea, mă întrebam dacă ar trebui să scap de caractere precum semnul dolar [$], semnul procentului [%] și poate altele. Ar fi putut interogarea să interpreteze semnul dolarului ca o variabilă PHP? Ce zici de sintaxa LIKE pe care am auzit-o că folosește simbolul % sau chiar un wildcard? Declarațiile pregătite ar trebui să aibă grijă din punct de vedere tehnic de toate acestea, dar am vrut doar să fiu în siguranță și să mă asigur că am acoperit totul în mod corespunzător. În cazurile în care uit să folosesc declarații pregătite sau pur și simplu le neglijez, am sperat ca această a doua linie de apărare să-mi spună că pot scăpa de amețeli.

Iată ce folosesc în prezent pentru a scăpa:

Funcția escape($connection, $data)( $new_data = trim($data); $new_data = i_real_escape_string($connection, $new_data); $new_data = addcslashes($new_data, "%_$"); $new_data = htmlspecialchars ($noi_date, ENT_NOQUOTES); returnează $noi_date; )

Deci este corect? Fac ceva teribil de greșit? Vă rugăm să rețineți că, atunci când returnez datele bazei de date, va trebui să elimin barele oblice inverse înainte de caracterele $,% și _.

Fac ceva teribil de greșit?

În primul rând, despre cercetarea dvs.

Declarații întocmite - singurul lucru minunat pe care l-ai găsit.

Deși folosirea mysqli_real_escape_string (presupunând că utilizați instrucțiuni pregătite) ar fi inutil și dăunător(creând un rezultat pe care l-ați notat singur: „L-ați sunat este\t...”).

Și Citatele Magice au fost de mult eliminate din limbă - prin urmare, nu merită nimic.

Deci, chiar și majoritatea premiselor tale inițiale sunt în mod clar greșite.

Acum la întrebarea ta.

Ar fi putut interogarea să interpreteze semnul dolarului ca o variabilă PHP?

Ce zici de sintaxa LIKE pe care am auzit-o că folosește simbolul % sau chiar un wildcard?

Da, ai auzit bine. Scopul exact al operatorului LIKE este de a efectua o căutare a modelului. Dezactivarea acestor caractere din LIKE nu ar avea cel mai mic sens.

De fiecare dată când veți utiliza operatorul LIKE, trebuie să decideți ce caracter specific să utilizați și pe care să nu permiteți. Nu puteți folosi o soluție unică. Ca să nu mai vorbim că în toate celelalte interacțiuni mysql semnul % nu are o semnificație specială.

Declarațiile pregătite ar trebui să se ocupe tehnic de toate acestea

Declarațiile pregătite nu au nimic de-a face cu semnele $ sau %. Instrucțiunile pregătite se referă la injecția SQL, dar niciun caracter nu o poate provoca (nu ați putea numi „injectare” un operator LIKE utilizat corespunzător, nu-i așa?).

În sfârșit, în partea cea mai rea.

În cazul în care uitați să folosiți declarații pregătite sau pur și simplu neglijați să le urmați,

nimic nu te va salva.

Și cel mai mic ajutor ar fi din funcția pe care ați dezvoltat-o.

Rezuma.

  1. Scapă de această caracteristică.
  2. Utilizare umpluturi * pentru a reprezenta fiecare variabilă individuală din interogare.
  3. Escape % și _ caractere din intrare numai dacă vor fi folosite în operatorul LIKE și nu doriți ca acestea să fie interpretate.
  4. Utilizați htmlspecialchars() pentru ieșire, nu pentru intrarea mysql.

*citiți declarațiile pregătite dacă acest termen nu vă este familiar.

Nu trebuie să evitați semnul dolarului. MySQL nu se uită la acest caracter în mod specific, iar PHP îl recunoaște doar în codul sursă, nu în valorile șirului (cu excepția cazului în care numiți eval pe șir, dar acesta este un cu totul alt vierme de viermi).

Ar trebui să scăpați doar de % și _ dacă utilizați introducerea utilizatorului ca argument LIKE și nu doriți ca utilizatorul să poată folosi metacaracterele. Acest lucru se poate întâmpla dacă procesați un formular de căutare. Nu trebuie să îl utilizați atunci când îl stocați într-o bază de date.

Nu trebuie să utilizați htmlspecialchars atunci când accesați baza de date. Acest lucru ar trebui să fie utilizat numai atunci când se afișează date utilizatorului pe o pagină HTML pentru a preveni injectarea XSS.

În funcție de ce date și pentru ce sunt folosite.

Dacă descoperiți că instrucțiunile implicite din PHP sunt prea mari și complexe, vă sugerez să aruncați o privire la unele dintre clasele disponibile pe github pentru a vă face o idee despre interogările simplificate.

Un exemplu de inserare de interogări cu această clasă

$date = Array ("login" => "admin", "activ" => adevărat, "firstName" => "Ioan", "lastName" => "Doe", "parolă" => $db->func( "SHA1(?)",Matrice ("secretparola+sare")), // parola = SHA1 ("secretparola+sare") "createdAt" => $db->now(), // createdAt = NOW() " expiră" => $db->acum("+1Y") // expiră = ACUM() + interval 1 an // Intervale acceptate [s]secundă, [m]minut, [h]oră, [d]zi, [Lună an); $id = $db->insert("utilizatori", $date); dacă ($id) echo "a fost creat utilizatorul. Id=" . $id; else echo "inserarea eșuată: " . $db->getLastError();