03/29/2024

Ce este injectarea SQL și cum să previi în aplicațiile PHP?

Deci, credeți că baza dvs. de date SQL este performantă și protejată de distrugerea instantanee? Ei bine, SQL Injection nu este de acord!

Da, este vorba de distrugere instantanee despre care vorbim, pentru că nu vreau să deschid acest articol cu ​​terminologia obișnuită de „înăsprire a securității” și „prevenirea accesului rău intenționat”. Injecția SQL este un truc atât de vechi din carte, încât toată lumea, fiecare dezvoltator, știe foarte bine despre el și este bine conștient de cum să o prevină. Cu excepția acelui moment ciudat în care alunecă, iar rezultatele pot fi deloc dezastruoase.

Dacă știți deja ce este SQL Injection, nu ezitați să treceți la a doua jumătate a articolului. Dar pentru cei care abia se înfățișează în domeniul dezvoltării web și visează să preia mai multe roluri de conducere, o introducere este necesară.

Ce este SQL Injection?

Cheia pentru înțelegerea SQL Injection se află în numele său: SQL + Injection. Cuvântul „injectare” aici nu are nicio conotație medicală, ci mai degrabă este folosirea verbului „injectare”. Împreună, aceste două cuvinte transmit ideea de a pune SQL într-o aplicație web.

Introducerea SQL într-o aplicație web. . . hmmm . . . Nu asta facem oricum? Da, dar nu vrem ca un atacator să ne conducă baza de date. Să înțelegem asta cu ajutorul unui exemplu.

Să presupunem că construiți un site web PHP tipic pentru un magazin local de comerț electronic, așa că decideți să adăugați un formular de contact ca acesta:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

Și să presupunem că fișierul send_message.php stochează totul într-o bază de date, astfel încât proprietarii magazinului să poată citi mesajele utilizatorilor mai târziu. Poate avea un cod ca acesta:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Deci, mai întâi încercați să vedeți dacă acest utilizator are deja un mesaj necitit. Interogarea SELECT * din mesajele în care nume = $nume pare destul de simplă, nu?

GRESIT!

În nevinovăția noastră, am deschis porțile distrugerii instantanee a bazei noastre de date. Pentru ca acest lucru să se întâmple, atacatorul trebuie să îndeplinească următoarele condiții:

  • Aplicația rulează pe o bază de date SQL (astăzi, aproape fiecare aplicație este)
  • Conexiunea curentă la baza de date are permisiuni de „editare” și „ștergere” în baza de date
  • Numele tabelelor importante pot fi ghicite
  Cum să utilizați Vana Portrait pentru a genera autoportrete AI

Al treilea punct înseamnă că acum că atacatorul știe că conduceți un magazin de comerț electronic, foarte probabil că stocați datele comenzilor într-un tabel de comenzi. Înarmat cu toate acestea, tot ce trebuie să facă atacatorul este să furnizeze acesta ca nume:

Joe; trunchie comenzi;? Da domnule! Să vedem ce va deveni interogarea când va fi executată de scriptul PHP:

SELECT * FROM mesaje WHERE nume = Joe; comenzile trunchiate;

Bine, prima parte a interogării are o eroare de sintaxă (fără ghilimele în jurul „Joe”), dar punct și virgulă forțează motorul MySQL să înceapă să interpreteze unul nou: ordinele de trunchiere. La fel, dintr-o singură lovitură, întregul istoric al comenzilor a dispărut!

Acum că știți cum funcționează SQL Injection, este timpul să vedeți cum să o opriți. Cele două condiții care trebuie îndeplinite pentru o injecție SQL cu succes sunt:

  • Scriptul PHP ar trebui să aibă privilegii de modificare/ștergere în baza de date. Cred că acest lucru este valabil pentru toate aplicațiile și nu veți putea face aplicațiile dvs. doar pentru citire. 🙂 Și ghiciți ce, chiar dacă eliminăm toate privilegiile de modificare, injecția SQL poate permite totuși cuiva să execute interogări SELECT și să vadă toată baza de date, inclusiv datele sensibile. Cu alte cuvinte, reducerea nivelului de acces la baza de date nu funcționează, iar aplicația dumneavoastră oricum are nevoie de el.
  • Intrarea utilizatorului este în curs de procesare. Singurul mod în care poate funcționa injecția SQL este atunci când acceptați date de la utilizatori. Încă o dată, nu este practic să oprești toate intrările pentru aplicația ta doar pentru că ești îngrijorat de injectarea SQL.
  • Prevenirea injectării SQL în PHP

    Acum, având în vedere că conexiunile la baze de date, interogările și intrările utilizatorilor fac parte din viață, cum prevenim injectarea SQL? Din fericire, este destul de simplu și există două moduri de a face acest lucru: 1) igienizați intrarea utilizatorului și 2) folosiți declarațiile pregătite.

    Dezinfectează intrarea utilizatorului

    Dacă utilizați o versiune PHP mai veche (5.5 sau mai mică, iar acest lucru se întâmplă foarte des în găzduirea partajată), este înțelept să rulați toate datele introduse de utilizator printr-o funcție numită mysql_real_escape_string(). Practic, ceea ce face elimină toate caracterele speciale dintr-un șir, astfel încât acestea să-și piardă sensul atunci când sunt utilizate de baza de date.

    De exemplu, dacă aveți un șir ca eu sunt un șir, caracterul ghilimele simple (‘) poate fi folosit de către un atacator pentru a manipula interogarea bazei de date creată și pentru a provoca o injecție SQL. Rularea prin mysql_real_escape_string() produce I’m a string, care adaugă o bară oblică inversă la ghilimele unice, scăpând-o. Ca rezultat, întregul șir este acum transmis ca șir inofensiv în baza de date, în loc să poată participa la manipularea interogărilor.

      Cum să aliniați textul după un marcator în PowerPoint

    Există un dezavantaj cu această abordare: este o tehnică cu adevărat, foarte veche, care merge împreună cu formele mai vechi de acces la baze de date în PHP. Din PHP 7, această funcție nici nu mai există, ceea ce ne duce la următoarea noastră soluție.

    Utilizați declarații pregătite

    Declarațiile pregătite sunt o modalitate de a face interogările bazei de date mai sigur și mai fiabil. Ideea este că, în loc să trimitem interogarea brută la baza de date, mai întâi îi spunem bazei de date structura interogării pe care o vom trimite. Aceasta este ceea ce înțelegem prin „pregătirea” unei declarații. Odată ce o declarație este pregătită, transmitem informațiile ca intrări parametrizate, astfel încât baza de date să poată „umple golurile” prin conectarea intrărilor la structura de interogare pe care am trimis-o înainte. Acest lucru elimină orice putere specială pe care le-ar putea avea intrările, făcându-le să fie tratate ca simple variabile (sau sarcini utile, dacă doriți) în întregul proces. Iată cum arată declarațiile pregătite:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $firstname = "John";
    $lastname = "Doe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Mary";
    $lastname = "Moe";
    $email = "[email protected]";
    $stmt->execute();
    
    $firstname = "Julie";
    $lastname = "Dooley";
    $email = "[email protected]";
    $stmt->execute();
    
    echo "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Știu că procesul sună inutil de complex dacă ești începător cu declarațiile pregătite, dar conceptul merită efortul. Iată o introducere plăcută la ea.

    Pentru cei care sunt deja familiarizați cu extensia PHP PDO și care o folosesc pentru a crea declarații pregătite, am un mic sfat.

    Avertisment: Aveți grijă când configurați PDO

    Când folosim PDO pentru accesul la baza de date, putem fi absorbiți de un fals sentiment de securitate. „Ah, bine, folosesc DOP. Acum nu mai trebuie să mă gândesc la nimic altceva” – așa merge gândirea noastră în general. Este adevărat că PDO (sau instrucțiunile pregătite MySQLi) este suficient pentru a preveni tot felul de atacuri de injecție SQL, dar trebuie să fii atent când îl configurați. Este obișnuit să copiați și să inserați codul din tutoriale sau din proiectele dvs. anterioare și să continuați, dar această setare poate anula totul:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    Ceea ce face această setare este să îi spună lui PDO să emuleze instrucțiunile pregătite, mai degrabă decât să folosească de fapt funcția de instrucțiuni pregătite a bazei de date. În consecință, PHP trimite șiruri de interogare simple în baza de date, chiar dacă codul tău pare că creează instrucțiuni pregătite și setează parametri și toate astea. Cu alte cuvinte, ești la fel de vulnerabil la injecția SQL ca înainte. 🙂

      7 sfaturi pentru a-ți atinge obiectivele de lectură mai repede cu Apple Books

    Soluția este simplă: asigurați-vă că această emulare este setată la false.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Acum, scriptul PHP este forțat să folosească instrucțiuni pregătite la nivel de bază de date, prevenind tot felul de injecție SQL.

    Prevenirea utilizării WAF

    Știți că puteți proteja și aplicațiile web de injectarea SQL folosind WAF (paravan de protecție pentru aplicații web)?

    Ei bine, nu doar injecția SQL, ci multe alte vulnerabilități de nivel 7, cum ar fi scripting-ul încrucișat, autentificarea întreruptă, falsificarea între site-uri, expunerea datelor etc. Fie puteți utiliza auto-găzduit, cum ar fi Mod Security sau bazat pe cloud, după cum urmează.

    Injecție SQL și cadre PHP moderne

    Injectarea SQL este atât de comună, atât de ușoară, atât de frustrantă și atât de periculoasă încât toate cadrele web PHP moderne sunt încorporate cu contramăsuri. În WordPress, de exemplu, avem funcția $wpdb->prepare(), în timp ce dacă utilizați un cadru MVC, acesta face toată munca murdară pentru dvs. și nici nu trebuie să vă gândiți la prevenirea injectării SQL. Este puțin enervant că în WordPress trebuie să pregătiți declarații în mod explicit, dar hei, despre WordPress vorbim. 🙂

    Oricum, ideea mea este că generația modernă de dezvoltatori web nu trebuie să se gândească la injecția SQL și, ca urmare, nici măcar nu sunt conștienți de această posibilitate. Ca atare, chiar dacă lasă o ușă din spate deschisă în aplicația lor (poate că este un parametru de interogare $_GET și vechile obiceiuri de a lansa o interogare murdară), rezultatele pot fi catastrofale. Așa că este întotdeauna mai bine să vă faceți timp pentru a vă scufunda mai adânc în fundații.

    Concluzie

    SQL Injection este un atac foarte urât asupra unei aplicații web, dar este ușor de evitat. După cum am văzut în acest articol, să fiți atenți atunci când procesați intrarea utilizatorului (apropo, SQL Injection nu este singura amenințare pe care o aduce gestionarea intrărilor utilizatorului) și interogarea bazei de date este tot ce este posibil. Acestea fiind spuse, nu lucrăm întotdeauna în securitatea unui cadru web, așa că este mai bine să fim conștienți de acest tip de atac și să nu ne îndrăgim.