Vă considerați baza de date SQL ca fiind eficientă și imună la distrugerea bruscă? Ei bine, injecția SQL are o altă părere!
Da, vorbim despre un pericol real și imediat, deoarece scopul acestui articol este să explicăm gravitatea atacului, nu doar să vorbim despre măsuri de „consolidare a securității” și „prevenirea accesului neautorizat”. Infiltrările SQL sunt un truc atât de bine cunoscut, încât fiecare dezvoltator, în mod normal, ar trebui să fie conștient de el și să știe cum să-l evite. Cu toate acestea, există momente când greșeli se strecoară, cu consecințe extrem de grave.
Dacă sunteți familiarizat cu conceptul de injecție SQL, vă puteți îndrepta către a doua parte a articolului. Dar, pentru cei care fac primii pași în dezvoltarea web și aspiră la roluri de conducere, o introducere este esențială.
Ce reprezintă injecția SQL?
Înțelegerea esenței injecției SQL rezidă în denumirea sa: SQL + Infiltrare. Termenul „infiltrare” nu are conotații medicale, ci se referă la acțiunea de a introduce ceva. Combinate, cele două cuvinte sugerează introducerea codului SQL într-o aplicație web.
A introduce SQL într-o aplicație web… hmm… nu facem asta oricum? Da, dar nu dorim ca un atacator să ne controleze baza de date. Vom înțelege acest lucru printr-un exemplu concret.
Să presupunem că dezvoltați un site web PHP pentru un magazin online local și doriți să adăugați un formular de contact, similar cu acesta:
<form action="record_message.php" method="POST"> <label>Numele dvs.</label> <input type="text" name="name"> <label>Mesajul dvs.</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Trimite"> </form>
Presupunem că fișierul send_message.php salvează toate informațiile într-o bază de date, pentru ca proprietarii magazinului să poată consulta mesajele ulterior. Codul ar putea arăta astfel:
<?php $name = $_POST['name']; $message = $_POST['message']; // verifică dacă utilizatorul are deja un mesaj mysqli_query($conn, "SELECT * from messages where name = $name"); // Alt cod aici
Inițial, se verifică dacă utilizatorul a trimis deja un mesaj. Interogarea `SELECT * from messages where name = $name` pare simplă, nu?
GREȘIT!
Din neatenție, am deschis ușa către o posibilă distrugere a bazei de date. Pentru ca acest lucru să aibă loc, atacatorul trebuie să îndeplinească următoarele condiții:
- Aplicația trebuie să ruleze pe o bază de date SQL (majoritatea aplicațiilor de astăzi fac acest lucru)
- Conexiunea curentă la baza de date trebuie să aibă permisiuni de „editare” și „ștergere”
- Numele tabelelor importante trebuie să poată fi ghicite
Al treilea punct înseamnă că, dacă atacatorul știe că administrați un magazin online, este foarte probabil să stocați detaliile comenzilor într-o tabelă numită „comenzi”. Având aceste informații, atacatorul trebuie doar să introducă următorul text în câmpul pentru nume:
Joe; trunchie comenzi;? Da! Să vedem cum va arăta interogarea când este executată de scriptul PHP:
SELECT * FROM mesaje WHERE nume = Joe; trunchie comenzi;
În regulă, prima parte a interogării conține o eroare de sintaxă (lipsesc ghilimelele în jurul lui „Joe”), dar punctul și virgulă obligă motorul MySQL să interpreteze o nouă instrucțiune: „trunchie comenzi”. Astfel, dintr-o singură mișcare, tot istoricul comenzilor este șters!
Acum că înțelegeți cum funcționează injecția SQL, este timpul să vedem cum o putem preveni. Cele două condiții esențiale pentru reușita unui atac de injecție SQL sunt:
Cum prevenim injecția SQL în PHP?
Având în vedere că interacțiunea cu bazele de date, interogările și primirea datelor de la utilizatori sunt aspecte esențiale, cum putem evita injectarea SQL? Din fericire, este destul de simplu și există două metode principale: 1) curățarea datelor introduse de utilizator și 2) folosirea declarațiilor pregătite.
Curățarea datelor introduse de utilizator
Dacă utilizați o versiune mai veche de PHP (5.5 sau mai jos, ceea ce se întâmplă frecvent în cazul găzduirii partajate), este indicat să rulați toate datele primite de la utilizatori printr-o funcție numită `mysql_real_escape_string()`. Această funcție elimină toate caracterele speciale dintr-un șir, astfel încât acestea să nu mai poată fi interpretate de baza de date.
De exemplu, un șir ca „Eu sunt un șir” conține ghilimele simple (`’`), care pot fi folosite de atacatori pentru a manipula interogările și a efectua o injecție SQL. Folosind `mysql_real_escape_string()`, șirul devine „Eu\’m un șir”, adăugând un backslash înainte de ghilimele, neutralizând astfel impactul lor. În acest fel, șirul este transmis ca un simplu text către baza de date, fără riscul de a modifica interogările.
Există un dezavantaj: această tehnică este învechită și se asociază cu metode mai vechi de acces la bazele de date în PHP. Începând cu PHP 7, această funcție nu mai este disponibilă, ceea ce ne conduce către următoarea soluție.
Utilizarea declarațiilor pregătite
Declarațiile pregătite sunt o modalitate de a face interogările bazei de date mai sigure și mai eficiente. Ideea este ca, în loc să transmitem direct interogarea completă către baza de date, mai întâi definim structura interogării. Aceasta este „pregătirea” declarației. După pregătirea declarației, transmitem datele ca parametri, permițând bazei de date să „umple spațiile libere” prin conectarea datelor la structura interogării definită anterior. În acest fel, datele nu mai pot modifica structura interogării, fiind tratate ca simple variabile. Iată un exemplu de declarații pregătite:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Creează conexiunea $conn = new mysqli($servername, $username, $password, $dbname); // Verifică conexiunea if ($conn->connect_error) { die("Conexiunea a eșuat: " . $conn->connect_error); } // pregătește și conectează parametrii $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // setează parametrii și execută $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 "Înregistrări noi create cu succes"; $stmt->close(); $conn->close(); ?>
Știu că acest proces poate părea complicat pentru un începător, dar conceptul este important. Aici găsiți o introducere mai detaliată.
Pentru cei familiarizați cu extensia PHP PDO și care o utilizează pentru crearea declarațiilor pregătite, am un mic sfat.
Atenție la configurarea PDO
Când folosim PDO pentru accesul la baza de date, ne putem crea un fals sentiment de siguranță. „Ah, folosesc PDO, deci nu mai trebuie să-mi fac griji” este o reacție frecventă. Este adevărat că PDO (sau declarațiile pregătite MySQLi) sunt eficiente pentru a evita injecțiile SQL, dar trebuie să fim atenți la configurare. Este normal să copiem codul din tutoriale sau proiecte anterioare, dar această setare poate anula toate eforturile:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Această setare îi spune lui PDO să simuleze declarațiile pregătite, în loc să folosească funcția dedicată a bazei de date. Astfel, PHP trimite șiruri simple de interogări către baza de date, chiar dacă codul pare să creeze declarații pregătite, să seteze parametri etc. În consecință, sunteți la fel de vulnerabili la injecția SQL ca înainte. 🙂
Soluția este simplă: asigurați-vă că emularea este setată la `false`:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Acum, scriptul PHP este obligat să folosească declarații pregătite la nivelul bazei de date, prevenind astfel injecția SQL.
Prevenirea prin utilizarea WAF
Știați că aplicațiile web pot fi protejate de injecția SQL folosind un WAF (paravan de protecție pentru aplicații web)?
Un WAF nu oferă doar protecție împotriva injecției SQL, ci și împotriva altor vulnerabilități de nivel 7, cum ar fi cross-site scripting, autentificarea defectuoasă, falsificarea intersite, expunerea datelor etc. Puteți utiliza un WAF auto-găzduit, cum ar fi Mod Security, sau o soluție bazată pe cloud.
Injecția SQL și cadrele PHP moderne
Injecția SQL este o problemă atât de comună, ușor de exploatat, frustrantă și periculoasă, încât toate cadrele web PHP moderne includ contramăsuri. În WordPress, de exemplu, există funcția `$wpdb->prepare()`, iar cadrele MVC se ocupă automat de prevenirea injecției SQL. În WordPress, trebuie să pregătiți explicit declarațiile, dar despre WordPress vorbim. 🙂
Scopul meu este să demonstrez că dezvoltatorii web moderni nu mai trebuie să se preocupe de injecția SQL și, ca urmare, nu sunt conștienți de această amenințare. Chiar dacă lasă o ușă deschisă în aplicație (de exemplu, un parametru `$_GET` și interogări directe), consecințele pot fi grave. De aceea, este important să aprofundați și principiile de bază.
Concluzie
Injecția SQL este un atac periculos, dar ușor de evitat. După cum am văzut, este esențial să fim atenți la procesarea datelor introduse de utilizator (injecția SQL nu este singura amenințare legată de acest aspect) și la interogările bazei de date. Nu lucrăm întotdeauna într-un cadru web sigur, deci este important să fim conștienți de acest tip de atac și să evităm neglijența.