Adesea, site-urile par să existe în primul rând pentru a pune ceva într-o bază de date pentru ao scoate mai târziu. În timp ce alte metode de baze de date, cum ar fi NoSQL, au câștigat popularitate în ultimii ani, datele pentru multe site-uri Web se află încă în baza de date tradițională SQL. Aceste date sunt adesea compuse din informații personale valoroase, cum ar fi numerele cărților de credit și alte informații personale de interes pentru identificarea hoților și criminalilor. Hackerii caută întotdeauna să obțină aceste date. Una dintre cele mai comune ținte ale acestor atacuri este bazele de date SQL care se află în spatele multor aplicații web printr-un proces de SQL Injection.
Un atac de injectare funcționează prin obținerea de către aplicație a unei intrări necredite la interpret. Recent, 40.000 de înregistrări ale clienților fiind preluate de la Bell Canada sunt rezultatul unui atac SQL Injection. La sfârșitul anului 2013, hackerii au furat peste 100.000 de dolari de la un ISP din California folosind injecție SQL.
Proiectul deschis pentru securitatea aplicațiilor web (OWASP) a ales atacul de injecție drept riscul de securitate al aplicației numărul unu în topul zece 2013, pe baza prevalenței sale și a riscului pentru sistemele web atacate. Din păcate, a deținut și poziția numărul unu în raportul precedent din 2010. Deci, ce este un atac SQL Injection? În acest tutorial voi discuta cum funcționează și ce puteți face pentru a vă proteja aplicația de aceste atacuri.
Orice sistem interpretat în spatele unui server web poate fi ținta unui atac de injectare. Cele mai comune obiective sunt serverele de baze de date SQL în spatele mai multor site-uri web. Injecția SQL nu provine direct din slăbiciunile din baza de date, dar utilizează deschiderile din aplicație pentru a permite atacatorului să execute declarații ale alegerii atacatorului pe server. Un atac de injectare SQL primește baza de date pentru a partaja mai multe informații decât este proiectată să furnizeze aplicația. Să ne uităm la un apel de bază de date SQL pe care l-ați putea scrie în ASP.NET.
Comanda SqlCommand = noul SqlCommand ("SELECT * FROM userdata WHERE UserId =" + id); Cititorul SqlDataReader = command.ExecuteReader ();
Ce e în neregulă cu acest cod? Poate nimic. Problema este aceea id
șirul pe care îl folosim. De unde provine acea valoare? Dacă o generăm intern sau dintr-o sursă de încredere, atunci acest cod ar putea funcționa fără probleme. Cu toate acestea, dacă obținem valoarea de la un utilizator și îl folosim fără modificări, tocmai ne-am deschis până la injecția SQL.
Să luăm un caz comun în cazul în care obținem parametrul ca să căutăm ca parte a adresei URL. Luați URL-ul http://www.example.com/user/details?id=123
. În ASP.NET folosind C # putem obține valoarea trecută folosind acest cod:
string id = Request.QueryString ["id"];
Acest cod, combinat cu apelul de mai sus, ne lasă deschisi la un atac. În cazul în care utilizatorul trece un ID de utilizator, cum ar fi 123, după cum era de așteptat, totul funcționează bine. Cu toate acestea, nimic nu am făcut aici, așa este cazul. Să presupunem că atacatorul încearcă să acceseze adresa URL http://www.example.com/user/details?id=0; SELECT * FROM userdata
.
În loc de a trece doar o valoare așa cum era de așteptat, am dat o valoare și apoi am adăugat o punct și virgulă, care termină o instrucțiune SQL. Se adaugă apoi oa doua instrucțiune SQL pe care atacatorul ar vrea să o execute. În acest caz, s-ar întoarce toate înregistrările în tabela userdata. În funcție de restul codului din pagina noastră, s-ar putea să se întoarcă o eroare sau poate afișa fiecărei înregistrări din baza de date atacatorului. Chiar și o eroare poate fi utilizată cu interogări construite cu atenție pentru a construi o vizualizare a bazei de date. Mai rău, imaginați-vă că atacatorul merge la o adresă URL http://www.example.com/user/details?id=0; DROP TABLE userdata
. Acum, toți utilizatorii dvs. sunt pierduți.
În acest exemplu, atacatorul poate rula orice cod pe care îl vrea pe serverul bazei de date. În cazul în care contul pe care îl solicită baza de date în baza de date are un control deplin asupra bazei de date, un scenariu prea comun, apoi abandonarea tabelelor și ștergerea înregistrărilor reprezintă o modalitate ușoară de a aduce un site în jos. Chiar dacă atacatorul poate citi și scrie date numai în baza de date, atunci transferul de date este doar o problemă de răbdare și de îngrijire.
Luați o bază de date simplă denumită "Produse" formată din trei coloane. Prima coloană conține id-ul produsului, al doilea conține numele produsului, iar al treilea conține prețul produsului. Pentru interogarea noastră normală vom încerca să găsim fiecare produs care conține un widget în nume. SQL-ul pentru a face acest lucru în același model arătat ar arăta astfel:
șir sql = "SELECT * FROM Produse WHERE nume de produs LIKE '%' + searchterm +"% '";
Un site tipic pentru a accesa acest lucru ar arăta http://www.example.com/product?search=widget
. Aici, pagina web va pur și simplu să circule prin fiecare înregistrare returnată și să o afișeze pe ecran. În acest caz vedem produsul nostru widget.
| produs | nume produs | preț | | ----------- | 1 | Widget | 100,00 |
Să ne schimbăm interogarea http://www.example.com/product?search=widget 'SAU 1 = 1;--
și executați aceeași interogare. vezi ceva mai mult. De fapt, vom vedea fiecare înregistrare în tabel.
| productid | nume produs | preț | | --- | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
Am înșelat interpretul să execute codul SQL ales de noi. SQL-ul rezultat rezultat va fi:
SELECT * FROM Produse WHERE nume de produs LIKE '% widget' SAU 1 = 1; -% '
Rezultatul va fi două declarații:
SELECT * FROM Produse WHERE nume de produs LIKE '% widget' SAU 1 = 1; -%“
Prin adăugarea 1 = 1
, care este întotdeauna adevărat, clauza unde va fi valabilă pentru fiecare rând din tabel și interogarea rezultată va reveni la fiecare rând. --
la începutul celei de-a doua instrucțiuni transformă restul instrucțiunii SQL într-un comentariu care împiedică mesajul de eroare pe care altfel l-am vedea.
Modificările afișajului nu pot împiedica această afirmație. Un atacator pacient nu poate folosi nimic altceva decât faptul că o valoare este returnată sau nu și interogări construite cu atenție pentru a cartografia încet baza de date și, eventual, pentru a prelua date chiar dacă acestea văd doar un mesaj de eroare. Există instrumente cum ar fi sqlmap pentru a automatiza procesul.
Împiedicați injectarea SQL prin împiedicarea intrării neimpozitate de la accesarea bazei de date SQL sau a altui interpret. Orice intrare din afara sistemului ar trebui considerată nesigură. Chiar și datele din alte sisteme partenere trebuie considerate nesigure, deoarece nu aveți nici o posibilitate de a garanta că celălalt sistem nu suferă de probleme de securitate care ar permite inserarea unor date arbitrare, apoi transmise aplicației dvs..
Revenind la exemplul nostru anterior, dacă știm că parametrul id ar trebui să fie întotdeauna un număr întreg, putem încerca să îl convertim la un număr întreg și să arătăm o eroare dacă aceasta nu reușește. O aplicație ASV.NET MVC ar face acest lucru folosind un cod similar cu acesta:
cantitate int; dacă (! int.TryParse (Request.QueryString ["qty"], cantitate excedentară)) returnare RedirectToAction ("Invalid");
Aceasta va încerca să convertească șirul la un întreg. Dacă conversia eșuează, codul se redirecționează la o acțiune care va afișa un mesaj nevalid.
Acest lucru poate fi prevenit dacă căutăm un parametru întreg. Nu ne-ar ajuta dacă așteptăm text ca în cazul căutării anterioare a produsului. Tehnica preferată în acest caz este de a folosi expresii regulate sau înlocuiri de șir pentru a permite doar caracterele necesare în valoarea trecută.
Acest lucru se poate face prin lista albă, procesul de eliminare a altor caractere decât setul specificat sau lista neagră, procesul de eliminare a unui membru dintr-un set specificat din șir. Lista albă este mai sigură, deoarece specificați numai caracterele permise. Pentru a elimina orice altceva decât literele și numerele dintr-un șir, putem folosi coduri precum:
Regex regEx = Regex nou ("[^ a-zA-Z0-9 -]"); șir filteredString = regEx (originalString, "");
Va trebui să evaluați orice intrare respectiv. Unele interogări ale bazei de date ar putea necesita o atenție mai specializată. Luați caractere care pot fi semnificative într-o comandă SQL, dar de asemenea ar putea fi un caracter valid într-un apel de bază de date. De exemplu, caracterul de cotație unică "este folosit pentru a porni și a termina un șir în SQL, dar ar putea fi, de asemenea, parte din numele unei persoane, cum ar fi O'Conner. În acest caz, înlocuirea cotei unice '
cu citate simple consecutive "
poate elimina problema.
Procedurile memorate sunt deseori văzute ca soluții pentru această problemă și pot face parte din soluție. Cu toate acestea, o procedură memorată greșit stocată nu vă va salva. Utilizați această procedură stocată care include un cod similar cu cel pe care l-am văzut deja pentru a construi o interogare:
ALTER PROCEDURE [searchProducts] @searchterm VARCHAR (50) = "AS BEGIN DECLARE @query VARCHAR (100) SET @query = 'SELECT * FROM Produse WHERE nume produs LIKE"% "+ @searchterm + (@query) END
Concatenarea șirului este problema. Să încercăm o simplă încercare în care încercăm să setăm o condiție întotdeauna adevărată pentru a afișa toate rândurile din tabel și să treacă în același "widget" SAU 1 = 1; - "ca interogare pe care am văzut-o mai devreme. :
| productid | nume produs | preț | | - | 1 | Widget | 100,00 | | 2 | Thingy | 50,00 | | 3 | Boxy | 125,00 |
În cazul în care sunt transmise date nesigure, avem același rezultat ca în cazul în care apelul a fost creat în codul nostru. Că concatenarea șirului are loc în interiorul unei proceduri stocate, în loc de codul nostru nu oferă protecție.
Următoarea piesă a puzzle-ului pentru a proteja împotriva atacurilor de injectare vine în folosirea parametrizării. Construirea de interogări SQL prin concatenarea șirurilor și apoi trecerea codului finit nu dă bazei de date nici o idee despre ce parte din șir este un parametru și ce face parte din comandă. Putem ajuta la protejarea împotriva atacurilor prin crearea de apeluri SQL într-un mod care să țină afirmații și valori distincte.
Putem rescrii procedeul stocat afișat mai devreme pentru a utiliza parametrii și pentru a produce un apel mai sigur. În loc să concatenăm %
caractere care reprezinta metacaractere, vom crea o noua reteta care adauga aceste caractere si apoi va transmite acest string nou ca parametru la instructiunea SQL. Noua procedură stocată arată astfel:
ALTER PROCEDURE [dbo] [SearchProductsFixed] @searchterm NVARCHAR (50) = "AS BEGIN DECLARE @query NVARCHAR (100) DECLARE @msearch NVARCHAR (55) SET @msearch = '%' + @searchterm + = 'SELECT * FROM Produse WHERE nume produs LIKE @search' EXEC sp_executesql @query, N '@ căutare VARCHAR (55)', @msearch END
Executarea acestei proceduri stocate doar cu cuvântul widget funcționează conform așteptărilor.
| productid | nume produs | preț ---- | 1 | Widget | 100.00
Și dacă trecem cu widget-ul nostru parametru "OR 1 = 1;" - rezultă că nimic nu este returnat arătând că nu mai suntem vulnerabili la acest atac.
Parametrizarea nu necesită proceduri stocate. De asemenea, puteți profita de aceasta cu interogări construite în cadrul codului. Iată un segment scurt în C # pentru a conecta și a rula o interogare împotriva serverului Microsoft SQL utilizând un parametru.
const string sql = "SELECT * FROM Produse WHERE nume produs LIKE @CategoryID"; var connString = WebConfigurationManager.ConnectionStrings ["ProductDatabase"]. folosind (var conn = nou SqlConnection (connString)) com comandă = nou SqlCommand (sql, conn); comanda.Parameters.Add ("@ searchterm", SqlDbType.NVarChar) .Value = string.Format ("% 0%", searchTerm); command.Connection.Open (); Cititorul SqlDataReader = command.ExecuteReader (); în timp ce (reader.NextResult ()) // Codul care se execută pe fiecare linie merge aici
Până în acest moment, am arătat cum să atenuez atacurile bazei de date. Un alt nivel important de apărare minimizează cauzele daunelor în cazul în care atacatorul depășește celelalte tipuri de apărare. Conceptul de privilegiu minim dă că un modul de cod care, în acest caz, solicită baza de date, ar trebui să aibă acces doar la informațiile și resursele necesare pentru scopurile sale.
Când se execută o comandă de bază de date, aceasta se face sub drepturile unui cont de utilizator. Castigam securitatea oferindu-i contului ca apelurile bazei de date sa ruleze doar sub baza drepturilor de a face lucrurile pe care trebuie sa le faca in mod normal. Dacă apelurile dintr-o bază de date ar trebui să citească numai date dintr-un tabel, atunci doar să le dai contului drepturile de selectare în tabel și să nu le inserați sau să le ștergeți. Dacă există tabele specifice, este necesar să se actualizeze, să se spună un tabel de comenzi, apoi să se introducă și să se actualizeze tabelul respectiv, dar nu și alt tabel, cum ar fi unul care conține informații despre utilizator.
Anularea comenzilor poate fi efectuată printr-un cont separat folosit numai pe pagina care efectuează acea activitate. Acest lucru ar împiedica eliminarea unei comenzi din tabel în altă parte mai dificilă. Folosind un cont de baze de date separate pentru funcțiile administrative ale site-ului, cu drepturile necesare mai mari decât cel pe care publicul îl folosește, poate face mult pentru a preveni deteriorarea unui utilizator care a găsit un atac injectabil deschis.
Acest lucru nu va împiedica toate atacurile. Nu ar face nimic pentru a împiedica returnarea rezultatelor suplimentare, cum ar fi exemplul anterior care arata tot conținutul unui tabel. Aceasta ar împiedica atacurile de la actualizarea sau ștergerea datelor.
Injecția SQL este cel mai periculos atac, mai ales atunci când țineți seama de cât de vulnerabile sunt site-urile web și cât de mult potențial are acest tip de atac la provocarea multor daune. Aici am descris atacuri SQL de injecție și am demonstrat daunele pe care le poate face. Din fericire, nu este atât de dificil să vă protejați proiectele web de această vulnerabilitate, urmând câteva reguli simple.
Nu aveți încredere în niciun fel de date exterioare introduse în aplicația dvs. Ar trebui să fie validată în fața unui listă albă a intrărilor valide înainte de a fi prelucrată în continuare. Aceasta poate însemna asigurarea faptului că un parametru întreg este de fapt un întreg sau o dată este o valoare de dată validă. Validați și textul pentru a include doar caracterele care ar necesita parametrul. În cazul unei căutări de text, puteți permite adesea numai litere și numere și filtrați semne de punctuație care ar putea fi problematice, cum ar fi semnul egal sau punct și virgulă.
Utilizați parametrizarea și evitați concatenarea șirului atunci când creați apeluri SQL. Procedurile memorate nu sunt un panaceu, deoarece acestea pot fi vulnerabile și în cazul în care se utilizează concatenarea simplă a șirului. Parametrarea evită multe dintre problemele de concatenare a șirului.
Codul care accesează baza de date ar trebui să funcționeze cu cele mai puține privilegii necesare pentru a finaliza sarcinile necesare. În puține situații, apelurile bazei de date utilizate de aplicația web trebuie să facă modificări în structura bazei de date, cum ar fi abandonarea sau modificarea tabelelor. Puteți adăuga un strat suplimentar de protecție prin rularea unor părți separate ale site-ului web sub diferite conturi. Contul bazei de date utilizat pentru acțiunile normale ale utilizatorilor nu are probabil nici un motiv să modifice tabelul care conține rolurile sau drepturile utilizatorilor. Rularea site-urilor administrative ale site-ului într-un cont mai privilegiat și secțiunea de utilizatori finali în cadrul unei categorii mai puțin privilegiate poate face mult pentru a diminua șansele unui cod care trece prin a provoca alte probleme.