QReferate - referate pentru educatia ta.
Cercetarile noastre - sursa ta de inspiratie! Te ajutam gratuit, documente cu imagini si grafice. Fiecare document sau comentariu il poti downloada rapid si il poti folosi pentru temele tale de acasa.



AdministratieAlimentatieArta culturaAsistenta socialaAstronomie
BiologieChimieComunicareConstructiiCosmetica
DesenDiverseDreptEconomieEngleza
FilozofieFizicaFrancezaGeografieGermana
InformaticaIstorieLatinaManagementMarketing
MatematicaMecanicaMedicinaPedagogiePsihologie
RomanaStiinte politiceTransporturiTurism
Esti aici: Qreferat » Documente informatica

Securitatea programelor



Course overview


Securitatea programelor poate fi asigurata doar daca tinem seama de cerintele de securitate in fiecare faza prin care trece un program: proiectare, programare, testare, implementare, utilizare. Scrierea codului sursa este la fel de importanta pentru securitate, pentru ca prin modul in care scriem programul putem sa-l expunem la riscuri sau, dimpotriva, sa-l protejam.


Probabil, cand il scriem trebuie sa ne gandim cine il va utiliza si cat de expus mediilor ostile va fi. Fara prea mare dificultate se poate constata ca majoritatea aplicatiilor utilizate astazi nu au fost concepute sa fie folosite in retele de calculatoare, lucru care le face foarte vulnerabile in momentul rularii lor in medii interconectate. Nu intamplator World Wide Web-ului i se mai spune si Wild Wild Web!




Problema nu este deloc simpla, caci nu totdeauna putem anticipa cine va fi utilizatorul programelor noastre si in ce medii le va rula, asa incat este bine sa scriem cod cat mai protejat.

Nu trebuie scapat din vedere ca efortul de a proiecta si scrie cod avand ca prim obiectiv securitatea este mult mai mare decat in mod obisnuit si conduce de cele mai multe ori la un cod mult mai robust.

Partea buna a acestui demers este insa faptul ca chiar si in acest caz, efortul este mai mic comparativ cu acela de a imbunatatit ulterior cu facilitati de securitate un cod deja proiectat si scris ca nesigur.

Si daca ne gandim la infinitatea modalitatilor de a ataca un program, realizam atat dificultatea, cat si importanta testarii cat mai multor variante de utilizare a codului.


Este de asemenea important de retinut faptul ca sistemele sigure sunt sisteme de calitate, astazi acceptandu-se ideea ca nu poate exista calitate fara securitate.


Cursul de protectia si securitatea codului sursa are ca obiectiv exemplificarea modului in care programele scrise in diverse limbaje pot fi atacate, din cauza scrierii unui cod intr-o oarecare masura, vulnerabil.

Pe langa aceasta, se arata modul in care pot fi evitate pe cat posibil situatiile in care un anumit program poate fi "spart", prin scrierea unui cod sigur.

Sunt descrise diferite tehnici si metode care ajuta la scrierea unui cod cat mai sigur, lucru care se traduce printr-o calitate sporita a programului, prin evitarea folosirii anumitor functii care prin natura lor sunt nesigure. Daca sunt totusi folosite, se arata ce context trebuie creat pentru ca rezultatul returnat sa nu fie unul neasteptat.


Se dau exemple de programe scrise in diferite limbaje de programare, carora la apel, le sunt furnizate mai multe perechi de date de intrare pentru a se observa modul in care reactioneaza ele si ce rezultate returneaza. Pentru fiecare caz in parte, este ilustrat modul in care programul poate deveni vulnerabil, rolul foarte important pe care il detin calitatea datelor de intrare si validarile care se fac.


Buffer overflows


Ne vom opri putin asupra virtutilor, dar si vulnerabilitatii lucrului cu pointeri sau accesului direct la memorie, in general. Limbajul C permite acest lucru, chiar multe din functiile de biblioteca primind ca parametri, pointeri. De multe ori o functie aloca dinamic spatiul, in raport cu nevoile de la o anumita rulare, iar o alta functie il primeste prin adresa si-l foloseste.

O depasire a buffer-ului are loc atunci cand intr-o zona de memorie alocata se incarca date care depasesc dimensiunea zonei alocate. In raport de modalitatile de alocare si de tipul memoriei folosite, deosebim urmatoarele tipuri de depasiri:

buffer overrun

heap overrun

array indexing errors.

2. 1 Buffer Overrun


Atat parametrii de apel ai unei functii, cat si variabilele locale nestatice se aloca pe stiva, in imediata apropiere a locului unde se memoreaza si adresa de revenire, retinuta pentru continuarea executiei la terminarea functiei apelate.

O anomalie apare atunci cand se furnizeaza date de intrare care depasesc zona alocata, suprascriind adresa de revenire. Spre exemplu, neverificarea datelor introduse de utilizator, transmise unei functii gen strcpy, permite rescrierea adresei de intoarcere, cu o alta adresa aleasa de un atacator, de obicei adresa unei functii scrise de acesta.

Codul sursa al unui program C++ ce demostreaza acest lucru este prezentat mai jos. Obiectivul consta in a executa functia hack scrisa de un atacator, prin modificarea adresei de intoarcere furnizata functiei func. Pentru a putea exemplifica, programul trebuie compilat din linie de comanda, deoarece mediul Microsoft Visual C++, verifica daca apar erori legate de stiva si preintampina depasirea zonei alocate.



Mai intai vom vedea ce raspuns ne ofera programul, in momentul transmiterii unui string cunoscut, in linie de comanda, pentru a repera unde tine sistemul datele si adresele celor doua functii implicate.


Acum, vom introduce un sir standard care sa depaseasca dimensiunea bufferului, pentru a observa unde anume sunt scrise datele si care din caracterele introduse suprascrie adresa de revenire. Se va obtine:


Dupa ce am introdus sirul de caractere mai lung, apare mesajul de eroare, sustinand ca aplicatia a incercat sa acceseze memoria la adresa 0x54535251, asa cum se poate vedea in figura 2.


Se observa faptul ca daca introducem in locul lui QRS valorile 0x45, 0x10, 0x40, am putea executa functia hack. Aceste valori nu se pot trasmite in linie de comanda, deoarece nu sunt afisabile, asadar vom face apel la un script perl numit static_buffer_overrun.pl pentru a trasmite valorile dorite.

Scriptul arata in felul urmator:



Ruland scriptul, rezultatul este cel banuit:

2.2 Heap Overrun


Alocarile de memorie se pot face nu numai pe stiva, ci si in segmentul de date al aplicatiei, memorie cunoscuta si sub numele de heap, datorita structurii de date folosita initial la gestiunea acestei categorii de memorie. O depasire a memoriei heap este similara depasirii unei stive, dar este, intr-un anume fel, mai greu de exploatat. Acest gen de atacuri exista insa si trebuie tratate cu toata seriozitatea, de catre programatori.

Daca pentru prevenirea suprascrierii datelor pe stiva, exista diferite instrumente de protectie (StackGuard, componente din mediul Visual C++ etc. ), acestea sunt mult mai rare in cazul suprascrierilor zonelor din heap.

Desi am putea ramane in domeniul programarii procedurale, vom programa totusi orientat obiect, pentru a arata ca nici aceasta nu ne scuteste de erorile lucrului defectuos cu pointeri.

Vom considera un obiect care tine prin adresa un sir de caractere; normal ar fi ca o astfel de clasa sa detina cele patru functii obligatorii in acest caz, pentru un cod sigur:

un constructor care sa faca alocarea in functie de contextul de rulare;

un destructor care sa dezaloce zona alocata prin constructor prevenind 'scurgerile de memorie' - memory leaks;

un constructor de copiere care sa previna partajarea de catre doua obiecte a aceleeasi zone de memorie, alocata sirului;

o suprascriere a operator= care sa previna partajarea de catre doua obiecte a aceleeasi zone de memorie prin copierea unui obiect in alt obiect.


Clasa noastra nu detine decat una din cele patru functii, scrisa corect (destructorul); in schimb, se prefera o functie utilitara pentru a initializa sirul, functie care nici nu face alocari, mizand ca functia apelant a facut alocarea si ca acea alocare corespunde necesarului de memorie, in fiecare caz in parte.



BadFunc este o functie externa clasei, prost alcatuita deoarece foloseste un obiect BadStringBuf caruia ar trebui sa-i asigure o utilizare care sa-i acopere lipsurile de proiectare, adica sa-i aloce zona membru pointata prin m_buf si sa verifice ca orice folosire a acesteia corespunde cu alocarea facuta. De observat si ca destructorul elibereza buffer-ul cu delete, desi zona fusese alocata cu malloc()



Sa urmarim modul de derulare a aplicatiei. Pentru demonstratie, in functia main se dau cateva valori convenabile argumentelor. Aceste valori ar trebui de fapt furnizate de catre atacator la momentul executiei programului. In continuare, vom face in asa fel incat sa afisam adresa la care se sare, ca mai apoi sa furnizam aceste valori ca siruri, functiei BadFunc.


Acum sa privim aplicatia din punctul de vedere al atacatorului. Observam ca aplicatia esueaza in cazul in care fie primul argument, fie al doilea devine prea lung, iar adresa de eroare (indicata in mesaj) arata coruperea memoriei dinamice. Apoi, vom rula programul intr-un debugger si vom urmari locatia primului sir. Ce memorie "importanta" poate fi in preajma acestui buffer? Dupa cateva investigatii, se observa ca urmatorul argument este scris intr-un alt buffer alocat dinamic - unde este pointerul catre buffer? Cautand in memorie dupa adresa celui de-al doilea buffer, aflam ca acesta se afla la 0x40 baiti distanta de locul unde incepe primul buffer. Acum, putem schimba acest pointer cu orice vrem, si orice string vom furniza drept al doilea argument, va putea fi rescris in orice punct din spatiul alocat aplicatiei!

Obiectivul exemplului este de a rula functia hack, asadar vom rescrie pointerul astfel incat sa refere 0x0012fe94, care in acest caz reprezinta punctul din stiva unde se gaseste adresa de intoarcere a functiei BadFunc. Analiza s-a facut folosind debuggerul din Visual C++ 6.0. Daca se foloseste altceva, deplasamentele si locatiile de memorie pot sa difere. Vom crea cel de-al doilea sir astfel incat sa seteze memoria de la 0x0012fe94 cu adresa functiei hack (0x0040100f). In aceasta abordare apare un lucru interesant - stiva nu a fost distrusa, deci, daca exista anumite mecanisme care monitorizeaza stiva, acestea nu vor observa nimic schimbat. La sfarsitul rularii aplicatiei, vom obtine urmatoarele rezultate:


Daca se considera ca acest exemplu e prea complex si ca nimeni nu poate descoperi cum se poate exploata, atunci ar trebui reconsiderata problema. Este demonstrat faptul ca se poate executa cod, chiar daca cele doua buffere nu sunt chiar foarte aproape, pacalind rutinele de management al memoriei dinamice.


2.3 Array Indexing Errors


Erorile legate de indexarea masivelor sunt mai putin exploatabile decat depasirile de buffer, dar pana la urma se refera la acelasi lucru - un string sau vector de caractere este astfel folosit incat scrie adresa unei functii la o adresa de memorie folosita de sistem ca adresa de revenire.

Codul sursa care demonstreaza acest lucru este cel din figura xxx.



Acum sa facem cateva calcule matematice. In exemplul curent, adresa de inceput a vectorului este 0x00510048, iar adresa pe care se doreste a fi rescrisa este, bineinteles, adresa de revenire de pe stiva, care e pastrata la 0x0012FF84.

Cunoscand modul de localizare a unui element in vector, deducem ca:




Este de notat faptul ca 0x 0012FF84 este folosit in loc de 0x0012FF84 (intr-un sistem de operare pe 32 de biti, 0x100000000 are aceeasi valoare cu 0x00000000). Din relatia de mai sus aflam ca indicele este 0x3FF07FCF, sau 1072725967 in zecimal, iar adresa functiei hack (0x00401000) este 4198400 in zecimal si trebuie scrisa peste elementul cu acest indice. Rezultatul este urmatorul:


Se observa ca tipul acesta de eroare este trivial de exploatat pentru un hacker care are acces la un debugger.


2.4 Prevenirea depasirilor de buffer


  1. pastrarea secreta a interiorului functiei si a modului de implementare, considerand ca lumea din afara functiei trebuie tratata ca fiind ostila si cu tendinte de distrugere. Accesibile exteriorului trebuie sa fie doar datele de intrare si de iesire.
  2. Intotdeauna intrarile de date trebuie validate.

  1. Manipularea in deplina siguranta a sirurilor de caractere

Manipularea incorecta de stringuri conduce la cele mai multe cazuri de depasiri de buffer, prin urmare este necesara revizuirea celor mai des folosite functii.

  • strcpy

Functia strcpy este prin natura ei una mai putin sigura si ar trebui folosita foarte rar sau chiar deloc. Prototipul functiei arata in felul urmator:

Numarul de cazuri in care aceasta functie poate sa nu mai functioneze corect este practic nelimitat. Daca bufferul destinatie este nul sau cel sursa, se intra in faza de tratare a exceptiilor. Daca bufferul sursa nu are terminator nul, rezultatele sunt nedefinite, depinzand de cat de repede se intalneste un octet nul. Cea mai mare problema apare atunci cand sirul de caractere din sursa este mai mare decat bufferul din destinatie. Atunci apare o depasire de buffer. Aceasta functie poate fi folosita numai in cazurile banale, atunci cand se copiaza un sir de caractere deja stiut intr-un buffer, pentru a prefixa un alt string.

Urmatorul exemplu arata cum se poate manipula aceasta functie intr-un mod cat mai sigur posibil:

Dupa cum se observa, se fac multe verificari de erori si daca inputul nu se termina cu caracterul nul, functia probabil ca va lansa o exceptie. Lucrul cu functia strcpy nu este deloc sigur. Ca dovada, Microsoft a renuntat la aceasta functie in proaspatul mediu de dezvoltare a aplicatiilor, MS Visual Studio 2005. Functia poate fi folosita, dar nu se mai ofera nici o garantie.


  • strncpy

Functia strncpy este mai sigura decat ruda ei, dar are si aceasta cateva probleme. Forma functiei este:

Problemele evidente sunt tot cauzate de faptul ca se transmite un pointer nul sau unul nepermis ca sursa sau destinatie. O alta posibilitate de a comite o greseala este data de valoarea count, care poate fi incorecta. Se poate observa faptul ca daca bufferul sursa nu se termina cu nul, functia nu va esua. Exista insa o problema: nu exista nici o garantie cum ca bufferul destinatie se termina cu caracterul nul (functia lstrcpyn garanteaza acest lucru). Asadar, poate fi considerata o problema grava daca inputul utilizatorului este mai mare decat accepta bufferul. Acest lucru inseamna ori ca cineva incearca sa ne sparga programul, ori ca programul a fost scris prost. Functia strncpy nu verifica daca inputul bufferului a fost prea lung. Sa analizam in continuare un exemplu:


Aceasta functie va esua numai daca inputul este reprezentat de un pointer nepermis. Prin folosirea operatorului sizeof se pot evita multe probleme, putand oricand modifica dimensiunea bufferului, fara a altera rezultatele. Mai mult decat atat, ar trebui setat tot timpul ultimul caracter din buffer ca fiind nul. Problema care apare aici este ca nu se stie daca inputul utilizatorului a fost prea lung. In documentatia aferenta strncpy se specifica faptul ca nu se returneaza nici o valoare in cazul in care apare vreo eroare. Multi poate considera ca este suficient daca bufferul este trunchiat, putand continua aplicatia, gandind ca undeva in cod, mai jos, vor trata eroarea. Acest lucru e gresit. Eroarea trebuie tratata cat mai aproape de locul unde a fost cauzata Depanarea este astfel, mult mai facila. Este si mult mai eficient - de ce sa se execute mai multe instructiuni decat este necesar? In sfarsit, trunchierea poate avea loc intr-un loc care poate cauza rezultate neasteptate, pornind de la o hiba de securitate pana la surprinderea utilizatorului. Codul urmator va rezolva aceasta problema:


Functie TrateazaInput_Strncpy2 este mult mai robusta. Schimbarile care se fac sunt: se seteaza ultimul caracter pe nul pentru a putea testa, apoi se copiaza intregul buffer, nu sizeof(buf)-1, dupa care se verifica daca intr-adevar ultimul caracter (nul) a ramas inca nul.


  • sprintf

Functia sprintf este la fel de daunatoare ca si strcpy. Aproape ca nu exista nici un mod de tratare a acestei functii intr-un mod sigur. Forma ei este:


Exceptand cazurile banale, nu este deloc usor de verificat daca bufferul este destul de lung pentru date, inainte de a apela functia sprintf. Sa analizam urmatorul exemplu:


In cate moduri poate aceasta functie esua? Daca msg nu are terminator, SprintfEroare probabil va lansa o exceptie. S-au folosit 21 de caractere pentru a reprezenta eroarea. Argumentul err poate afisa pana la 10 caractere, si argumentul linie poate retine pana la 11 caractere. Asadar, este sigur sa se transmita doar 89 de caractere pentru string msg. Reamintirea numarului de caractere care poate fi folosit de diferitele formate este dificila. De asemenea, ceea ce returneaza sprintf nu este de foarte mare ajutor. Se spun cate caractere au fost scrise, deci codul poate fi scris in felul urmator:


Nu ne este de foarte mare ajutor aceasta ultima varianta, deorece nu se stie cati octeti au fost rescrisi, poate a fost chiar rescris pointerul care trateaza exceptia! Nu se poate proceda la a trata exceptiile doar pentru a diminua efectul unei depasiri de buffer. Un atacator se poate folosi de rutinele de tratare a exceptiilor in atingerea scopului sau. Raul a fost deja provocat, atacatorul a castigat.


  • _snprintf

_snprintf are urmatoare forma:


Aceasta functie are aceeasi flexibilitate ca si _sprintf, si este sigur de utilizat. Sa analizam un exemplu:

Se pare ca in toate cazurile probleme pot aparea, indiferent de functiile care sunt folosite: _snprintf nu garanteaza daca bufferul destinatie are terminator, luand in calcul implementarea din Microsoft C run-time library, deci trebuie facuta verificarea de catre programator. Pentru a mai complica putin lucrurile, aceasta functie nu a facut parte din standardul C pana ce ISO C99 a fost adoptat. Deoarece _snprintf este o functie nestandard, de aceea incepe cu o liniuta de subliniere, in cazul in care se scrie un cod portabil pot apare situatiile:

  • functia returneaza un numar negativ daca bufferul a fost prea mic;
  • functia returneaza numarul de octeti care ar fi trebuit scrisi;
  • functia poate adauga sau nu un terminator de linie.

La scrierea unui cod portabil, este bine sa se utilizeze o functie care sa nu faca parte din codul principal si care sa verifice aceste lucruri. Se recomanda sa se specifice mereu dimensiunea sirului de caractere ca fiind cu una mai mica decat dimensiunea bufferului, astfel incat sa ramana loc pentru terminatorul de sir.

Concatenarea de siruri de caractere poate fi nesigura folosind functiile traditionale. Functii ca strcpy, strcat sunt nesigure, exceptand cazurile banale, iar strncat este dificil de utilizat. Folosind ­_snprintf , concatenarea se poate realiza usor si in siguranta.


Biblioteca standard de sabloane pentru sirurile de caractere


Unul din lucrurile care ne pot usura foarte mult munca in lucrul cu C++ este folosirea bibliotecii standard de sabloane (STL). Incovenientul reprezentat de faptul ca in C++ nu exista tipul string este acum eliminat. Vom prezenta in continuare un exemplu:


Dupa cum se observa, lucrurile sunt foarte simple. La fel este si in cazul concatenarii de siruri de caractere.


  • gets si fgets

Nu se poate incheia un capitol despre functii nesigure fara a mentiona gets. Functia gets este definita astfel:

Functia aceasta este totdeauna un dezastru pe cale sa se intample. Ea preia de la intrarea standard (stdin) pana cand primeste un LineFeed (trece la o noua linie) sau Carriage Return (Enter). Nu exista nici o posibilitate de a stii daca va exista o depasire de buffer sau nu. Deci, a nu se folosi gets, ci fgets sau un obiect stream din C++.


3. SQL Injection

3.1 Prezentare problema


SQL Injection este o 'tehnica' care permite unui atacator sa execute comenzi SQL neautorizate. Problema care apare e aceeasi ca si cea din capitolele anterioare si anume increderea in cine nu trebuie, crezand ca utilizatorul a furnizat date de intrare intr-o forma corecta, cand de fapt lucrurile nu stau deloc asa.

Variabila nume este cea furnizata de utilizator. Problema cu acest string SQL este ca atacatorul poate infiltra in variabila nume si alte clauze. Sa presupunem ca inputul este Sergiu, care creeaza urmatoare comanda SQL, inofensiva :

Totusi, ce s-ar intampla daca utilizatorul rau intentionat ar introduce:

Sergiu' or 1=1 -- . Va rezulta urmatoarea formulare:

Aceasta comanda va returna toate coloanele din tabela client pentru fiecare linie unde coloana nume este Sergiu. De asemenea, va returna si toate liniile care safisfac conditia 1=1. Dar cum 1=1 este adevarat pentru oricare linie din tabela, atacatorul va vedea toate liniile din tabela. Consecintele pot fi dezastruoase in cazul in care in tabela sunt tinute informatii confidentiale (parole, numere de card etc.). Imaginati-va urmarile, in cazul unei scheme a unei baze de date care arata in felul urmator:

Ultima parte din cerere este formata din caracterele "--". Aceste caractere reprezinta un operator de comentariu, care ii creeaza posibilitatea atacatorului sa realizeze o cerere valida, dar malitioasa. In acest fel, caracterele adaugate in continuarea cererii de catre programator nu sunt luate in considerare.

Operatorul comentariu "--" este suportat de majoritatea bazelor de date relationale, inclusiv MS SQL Server, IBM DB2, Oracle, PostgreSQL si MySql.

Exemplul prezentat anterior este denumit SQL Injection. Aceasta este un atac care schimba logica comenzii SQL, adaugand clauza or la comanda. Se mai pot modifica si in alte moduri comenzile SQL, folosind aceeasi tehnica, adaugand alte afirmatii SQL, apeland functii sau proceduri stocate.

In mod normal, multe servere de baze de date suporta executarea mai multor instructiuni intr-una singura. De exemplu, in SQL Server se poate formula:

care va executa cele doua comenzi afirmatii SQL.

Se pot, de asemenea, executa nu numai doua cereri, ci chiar manipula datele astfel incat sa poata fi adaugate, modificate sau sterse obiecte de tipul tabelelor sau procedurilor stocate, reguli sau vizualizari. A se urmari urmatorul exemplu care prezinta ce s-ar putea introduce in loc de nume:

Aceasta cerere interogheaza tabela dupa numele Sergiu, apoi sterge tabela client.

In continuare, vom urmari cum poate un utilizator sa stearga o tabela dintr-o baza de date, fiind conectat la un serviciu web sau un server web, prin prezentarea unui cod sursa de genul:





In primul rand, se observa ca fraza SQL se formeaza prin concatenare de stringuri, care conduce la injectia SQL.

In al doilea rand, numele de utilizator cu care se realizeaza conectarea la server este sa, ce reprezinta contul de administrare pentru SQL Server. Nu trebuie sa se foloseasca niciodata o conectare prin intermediul sa. Utilizatorul sa este pentru SQL Server ce este System pentru Windows NT si restul. De departe, sunt cele mai periculoase conturi in respectivele sisteme.

Alta eroare este cauzata de faptul ca parola, care este inclusa direct in cod. Daca este spart cumva codul, parola va fi aflata imediat.

Mai exista o eroare mult mai subtila: cand comanda SQL nu reuseste din diverse motive, atunci sunt furnizate detalii cu privire la cauza neexecutarii comenzii SQL. Acest lucru ajuta enorm un hacker in demersul lui de a produce pagube sau obtine informatii, deoarece este prezentata sursa erorilor.

3.2 Pseudoremediu #1 - Punerea intre apostrofuri a inputului


Punerea intre apostrofuri este o metoda de multe ori propusa spre a rezolva problemele legate de inputurile intr-o baza de date, dar nu este nicidecum un remediu. Sa observam cum se poate aplica si de ce nu reprezinta o varianta buna. Vom lua ca exemplu urmatorul fragment de cod:


Dupa cum se poate observa, codul inlocuieste un apostrof cu doua apostrofuri in inputul utilizatorului. Asadar, daca atacatorul incearca un nume de forma Sergiu' or 1=1 --, apostroful pus de atacator este inlocuit, facand comanda SQL invalida, inainte de comentariu. Aceasta va arata in felul urmator:

Si totusi, acest lucru nu-l dezarmeaza pe atacator; el va folosi in loc campul varsta, care nu e pus intre apostrofuri, pentru a ataca serverul. De exemplu, varsta poate fi 24 shutdown --. Nu apar apostrofuri, si ca atare serverul se va opri.

Concluzia este ca punerea intre apostrofuri a inputului nu ne face imuni la injectiile SQL.

3.3 Pseudoremediu #2 - Folosirea de proceduri stocate


Multi dezvoltatori considera gresit ca folosirea de proceduri stocate fac o aplicatie imuna la atacurile de tip SQL Injection. Prin proceduri stocate se pot evita doar cateva tipuri de atacaturi. Vom prezenta in continuare un model de procedura stocata numita sp_GetDetalii:


Daca introducem Sergiu' or 1=1 -- vom esua deoarece nu pot exista asocieri la apelul unei proceduri stocate. Urmatoarea sintaxa SQL este incorecta:

Totusi, manipularea datelor este permisa:

Aceasta comanda va aduce date despre Sergiu si apoi va introduce o noua linie in tabela! Dupa cum se poate observa, folosirea de proceduri stocate nu poate face codul sigur in fata atacurilor de tip SQL Injection.

Cel mai inspaimantator exemplu de folosire a procedurilor stocate din punctul de vedere al securitatii este o procedura stocata care arata in felul urmator:


Se poate ghici ce face acest cod? Executa pur si simplu ceea ce introduce utilizatorul, desi codul apeleaza o procedura stocata. Din fericire, acest gen de proceduri sunt foarte rar intalnite.


Putin mai jos se remarca imaginea modulului care implementeaza metodele tocmai prezentate.

Figura 4: Imaginea grafica a unui modul din aplicatie



Dupa cum se poate observa, pseudoremediile pot doar ajuta intrucatva, dar nu sunt sigure. In continuare sa urmarim cateva remedii.


3.4 Remediu - A nu se conecta niciodata ca sysadmin


Daca conectarea la baza de date se realizeaza cu un cont gen sysadmin si exista o hiba in cod, de genul celor care permit injectia SQL, atunci atacatorul poate indeplini orice sarcina, incluzand urmatoarele:

  • stergerea oricarei baze de date sau tabele din sistem
  • stergerea oricaror date din orice tabela
  • modificarea oricaror date din orice tabela
  • modificarea oricarei proceduri stocate, trigger sau regula
  • stergerea de jurnale
  • adaugarea de noi utilizatori in sistem
  • apelarea oricaror proceduri stocate ale administratorului sau proceduri stocate extinse

Potentialul de a produce pagube e nelimitat. O posibilitate de a diminua riscul este suportul pentru conectari autentificate folosind autentificarea si autorizarea sistemelor de operare setand Trusted_Connection=True in stringul de conectare. Daca nu se pot folosi tehnicile de autentificare native - care cateodata ar trebui evitate - se poate crea un cont specific caruia sa i se acorde toate drepturile de citire, scriere si actualizare a datelor din baza de date si cu acesta sa se realizeze conectarea la baza de date. Acest cont ar trebui verificat periodic pentru a determina ce privilegii are in sistem si asigurat ca administratorul nu i-a acordat accidental drepturi care pot compromite sistemul.

Probabil cel mai periculos aspect al conectarii ca sysadmin este dat de posibilitatea ca se pot executa orice procedura stocata administrativa. De exemplu, SQL Server include proceduri stocate ca sp_cmdshell care permite atacatorului sa invoce comenzi de shell. Oracle include utl_file, care permite unui atacator sa citeasca sau sa scrie fisiere.

A se tine cont de faptul ca realizarea unei conectari la o baza de date ca sysadmin nu este numai un defect ci incalca si principiul cel mai putin privilegiat (care afirma ca un utilizator nu trebuie sa detina mai multe privilegii decat cele necesare pentru a-si desfasura munca). Oamenii isi contruiesc propriile aplicatii folosindu-se de sysadmin pentru conectare pentru ca totul functioneaza; nici o alta configuratie nu este necesara pentru serverul de la distanta. Din pacate, acest lucru inseamna ca totul functioneaza si pentru hackeri!

In cele ce urmeaza vom prezenta un exemplu complex pentru a ilustra cum se pot construi programe sigure in lucrul cu baze de date.


3.5 Remedii - Exemplu solid

Acum ca am vazut cateva greseli comune si cateva practici bune pentru a construi aplicatii sigure in lucrul cu baze de date, sa urmarim un exemplu sigur din punctul de vedere al securitatii. Codul ce urmeaza este scris in C# si poate fi inclus intr-un serviciu web, avand multiple niveluri de securitate. Daca un mecanism esueaza, cel putin un altul va proteja aplicatia si datele.



Mai multe niveluri de securitate sunt folosite aici, fiecare explicate in detaliu mai tarziu:

  • Parola lipsa nu este niciodata permisa in cazul conectarii la baza de date. Acesta e cazul in care administratorul creeaza din greseala un cont fara sa-i seteze parola.
  • Acest cod poate citi doar o cheie specifica din registru; nu poate desfasura alte operatiuni asupra registrului.
  • Codul efectueaza validari asupra inputului: poate contine de la 1 la 10 cifre. Orice altceva nu este permis.
  • Stringul de conectare la baza de date se gaseste in registru, nu in cod si nici in fisierul de configurare a serviciului web.
  • Codul foloseste o procedura stocata, in principal sa ascunda logica aplicatiei in cazul in care este compromisa.
  • Nu se poate observa in cod, dar conexiunea nu se foloseste de catre sa. Mai degraba, foloseste un cont cu cele mai putine privilegii care pemite interogarea si executarea asupra celor mai apropiate tabele.
  • Codul foloseste parametri, nu concatenare de stringuri, pentru a construi cererile.
  • Codul forteaza inputul sa fie reprezentat pe 64 de biti.
  • In caz de eroare, atacatorului nu i se precizeaza nimic, in afara de faptul ca s-a produs o eroare.
  • Conexiunea la baza de date este intotdeauna inchisa la sfarsit, indiferent daca codul esueaza sau nu.

Vom incepe prin a explica cele doua atribute de securitate, specifice mediului .NET Framework, de dinainte de apelul functiei. Primul, SQLClientPermissionAttribute, permite lui SQL Server .NET Data Provider sa asigure ca utilizatorul are un nivel de securitate adecvat pentru a accesa datele - in cazul de fata, prin setarea AllowBlankPassword pe false, folosirea de parole nule este interzisa. In cazul in care se intampla sa fie furnizata o parola nula, acest cod va lansa o exceptie.

Cel de-al doilea atribut, RegistryPermissionAttribute, limiteaza accesul la anumite chei din registru si cum pot fi acestea manipulate (citite, scrise s.a.m.d.). In cazul de fata, setand proprietatea Read pe @'HKEY_LOCAL_MACHINESOFTWARE Client', doar o cheie specifica, cea care detine stringul de conectare, poate fi citita. Chiar daca atacatorul poate "face" codul sa acceseze si alte parti ale registrului, va esua.

Apoi, codul obliga ca numarul introdus sa fie intre 1 si 10 cifre. Acest lucru este indicat prin expresia regulata ^d$, care cauta doar numere ce au minim o cifra si maxim 10 cifre de la inceput (^) pana la sfarsitul ($) inputului. Declarand ceea ce este un input valid si refuzand orice altceva, un atacator nu mai poate adauga afirmatii SQL la creditcardID.

Codul include si alte modalitati de aparare. Se observa ca obiectul SqlConnection este contruit pe baza unui string de conectare care se gaseste in registry. De asemenea, trebuie observata functia de acces la registru ConnectionString. Pentru a afla acest string, un atacator nu trebuie numai sa acceseze codul sursa ci sa acceseze si cheia din registru potrivita.

Datele din cheia din registru contin stringul de conectare:


Conectarea la baza de date se face prin intermediul unui cont specific, readuser, cu o parola greu de intuit. Acest cont poate doar citi si executa obiectele SQL potrivite in baza de date a clientului. Daca conexiunea e compromisa, atacatorul poate rula doar cateva proceduri stocate si interoga tabelele adecvate; el nu poate distruge baza de date master si nici nu poate savarsi atacuri ca stergerea, adaugarea sau modificarea datelor.

Comanda SQL nu este construita folosind tehnica nesigura de concatenare a sirurilor. In schimb, codul foloseste cereri parametrizate pentru a apela o procedura stocata. Apelul prin intermediul procedurilor stocate e mult mai rapid si mai sigur decat folosirea de siruri de caracatere concatenate, deoarece baza de date si tabelele nu sunt expuse, iar procedurile stocate sunt optimizate de motorul bazei de date.

A se observa faptul ca daca are loc o eroare, utilizatorului (atacatorului) nu i se spune nimic, atunci cand cererea nu e de pe local sau de pe aceeasi masina unde codul serviciului se afla. Daca ai acces fizic la serviciul web, "detii" calculatorul oricum! Se mai poate adauga codului cateva limitari cu privire la mesajele de eroare, acestea putand fi vazute doar de administratorul sistemului, in felul urmator:


Apoi, conexiunea este intotdeauna inchisa, in rutina finally. Daca o apare o exceptie in blocul try/catch, conexiunea este inchisa cu eleganta, evitand atacurile de tip DoS (Denial of Service) prin lasarea conexiunii deschise.











4. Securitatea socketului

4.1 Evitand spargerea serverului


Deturnarea unui server are loc atunci cand o aplicatie permite unui utilizator local sa intercepteze si sa manipuleze informatii destinate serverului, pe care utilizatorul local nu le-a determinat. In primul rand ne vom face o idee asupra modului in care se poate realiza acest lucru. Cand un server este pornit, prima data creeaza un socket si ataseaza acest socket conform protocolului utilizat. Daca e vorba de un Transmission Control Protocol (TCP) sau User Datagram Protocol (UDP) socket, socketul este atasat unui port. Mai putinele utilizate protocoale ar putea sa contina scheme foarte diferite de adresare. Un port este reprezentat de un intreg fara semn (16 biti) in C sau C++, deci poate lua valori intre 0 si 65535. Structura unui socket, in cazul protocolului IPv4, arata in felul urmator:


Cand un socket este atasat, membrii importanti sunt sin_port si sin_addr . In cazul unui server, aproape intotdeauna se specifica un port pe care sa se asculte, dar dificultatea apare cand se lucreaza cu campul sin_addr. Documentatia afirma ca daca legarea se realizeaza prin INADDR_ANY (adica 0), serverul asculta toate interfetele de retea. Daca atasarea se face la o adresa IP specifica, se urmaresc pachetele transmise doar acelei adrese. Problema neasteptata care apare este data de faptul ca se pot atasa mai multe socketuri la un singur port.

Librariile socketului decid cine castiga si primeste pachetul determinand care legare (atasare) e mai specifica. Un socket atasat la INADDR_ANY pierde in fata unui socket atasat la o adresa IP specifica. De exemplu, daca serverul are doua adrese IP, 157.34.32.56 si 172.101.92.44, software-ul socketului va permite transmiterea datelor prin socketul aplicatiei atasat la 172.101.92.44 decat aplicatiei care este legata prin INADDR_ANY. O solutie ar fi identificarea si atasarea tuturor adreselor IP de pe server, dar acest lucru este deranjant, avand in vedere faptul ca placile de retea nu raman tot timpul aceleasi, si ar fi nevoie de scrierea unui cod mult mai amplu. Din fericire, exista o scapare, care va fi ilustrata in urmatorul exemplu de cod. O optiune a socketului, numita SO_EXCLUSIVEADDRUSE, care a fost prima data introdusa in Microsoft Windows NT Service Pack 4, rezolva problema.
















In cele ce urmeaza vom analiza cum lucreaza acest cod si vom prezenta cateva rezultate.

In primul rand, se verifica argumentele. Exista doua optiuni disponibile: hijack si nohijack. Optiunea hijack foloseste SO_REUSEADDR care permite atacatorului sa se ataseze la un port activ. Optiunea nohijack foloseste SO_EXCLUSIVEADDRUSE, care previne ca SO_REUSEADDR sa functioneze. Daca nu se specifica nici o optiune, serverul se va atasa la port in mod normal. O data ce socketul este atasat, vom inregistra mesajul si de unde a plecat pachetul.


Asadar, se va urmari ce se va intampla cu serverul in cazul in care nu se va folosi SO_EXCLUSIVEADDRUSE. Se va invoca serverul victima cu urmatoarea comanda:


In continuare, se va invoca atacatorul prin: (se va inlocui 86.55.236.165 cu adresa IP specifica fiecaruia)

Acum se va folosi clientul pentru a trimite un mesaj:

Acestea sunt rezultatele din partea atacatorului:

Victima (serverul) vede aceasta:

Acum, se va prezenta modul sigur de pornire a unui server:

Atacatorul procedeaza la fel ca inainte:

Serverul raspunde cu:

Iar atacatorul primeste:


Acum, cand clientul va trimite un mesaj, serverul il va primi pe cel corect:

Exista un mic dezavantaj in cazul folosirii SO_EXCLUSIVEADDRUSE - daca aplicatie trebuie repornita, ar putea esua daca nu este oprita cum trebuie. Problema este ca, desi aplicatia a fost inchisa, mai pot exista conexiuni ratacite in stiva TCP/IP la nivelul sistemului de operare. Abordarea potrivita este de a apela shutdown pe socket si apoi apelat recv pana cand nu mai exista nici un fel de date si returneaza un cod de eroare. Apoi, se poate apela closesocket si redeschide aplicatiei.


In Microsoft Windows .NET Server 2003 nu mai este necesara folosirea lui SO_EXCLUSIVEADDRUSE. Va exista un DACL (Discretionary Access Control List) adecvat, care va permite atribuirea permisiunilor utilizatorului si administratorului si va fi aplicat unui socket. Aceasta abordare ne va absolvi de problema abordata mai sus, in legatura cu deturnarea serverului.






4.2 Alegand interfetele pentru Server


Cand se configureaza un server care urmeaza a fi expus direct la Internet, primul lucru care trebuie facut este reducerea numarului de servicii expuse lumii exterioare la minimum. Daca sistemul are doar o adresa IP si o singura placa de retea, realizarea acestui lucru e foarte simpla: se pot inchide serviciile pana cand porturile de care suntem ingrijorati nu mai asculta. Daca sistemul face parte dintr-un sit de anvergura, are cel putin doua placi de retea. In aceasta situatie lucrurile se complica. Nu se pot inchide serviciile in toate situatiile; se poate dori ca un serviciu sa fie activ la capat. Daca nu exista un control asupra carei placi de retea sau adresa IP un serviciu asculta, putem face o filtrare pe serverul gazda sau putem depinde de ruter sau de firewall. Oamenii pot configura gresit filtrele IP; ruterele pot cateodata sa esueze in diferite moduri; si daca un sistem alaturat este atacat, atacatorul poate probabil sa atace si serverul tocmai configurat, fara sa treaca prin ruter. In plus, daca serverul in cauza este foarte solicitat, un filtru pornit pe server poate insemna si el foarte mult. Un serviciu IP ar trebui sa fie configurabil la unul dintre cele trei nivele:


  • Pe ce placa de retea se asculta
  • Ce adresa IP sau adrese se vor asculta, si preferabil pe ce port se va asculta
  • Ce clienti se pot conecta la serviciu

Enumerarea interfetelor si atasarea adreselor IP acelor interfete era oarecum dificil de realizat in Windows NT 4, spre exemplu. Era necesara cautarea in registru a placilor de retea atasate, dupa care, pentru fiecare placa in parte, a cheilor individuale.


5. Protectie impotriva atacurilor de tip Denial of Service

Ocuparea maxima a procesorului


Programul este un bun exemplu pentru a testa cat de bine suporta o functie un input abuziv. Functia main este dedicata crearii sirului si afisarii informatiilor referitoare la performanta. Functia EliminaBackslash1 elimina nevoia de a mai aloca un buffer in plus, dar o face in detrimentul unui numar de instructiuni proportional cu patratul duplicatelor gasite. Functia EliminaBackslash1 foloseste un al doilea buffer, facand numarul de instructiuni proportional cu lungimea stringului. In tabelul urmator sunt afisate cateva rezultate:



Lungimea sirului

Timpul pentru EliminaBackslash1

Timpul pentru EliminaBackslash2

10

0 millisecunde (ms)

0 ms

100

0 ms

0 ms

1000

0 ms

0 ms

10,000

0 ms

0 ms

100,000

1,234 ms

0 ms

1,000,000

163,256 ms

15 ms


Dupa cum se poate observa, diferentele dintre cele doua functii nu apar pana cand lungimea sirului nu depaseste 100,000 de baiti. La 10 milioane de baiti, durata in cazul functiei EliminaBackslash1 este de aproximativ 7 ore pe un procesor AMD Athlon 64 la 3000 Mhz. Ne putem da seama ca, daca un atacator poate furniza cateva cereri de acest gen, serverul va fi blocat pentru ceva timp.

In urmatoarea imagine se constata cum programul demonstrativ (CPU_DoS) ocupa in intregime resursele procesorului sistemului:


Figura 5: Procesorul utilizat la maximum

Concluzii


Daca s-a retinut doar un singur lucru din citirea acestei lucrari, acesta ar trebui sa fie:


Acest lucru inseamna construirea de software sigur si de calitate, care respecta "Principle of Least Privilege" (ce afirma ca o aplicatie ar trebui sa poata sa ruleze folosind numai drepturile si privilegiile de care are nevoie pentru a-si duce la indeplinire sarcinile), care are multiple niveluri de aparare, facandu-l foarte greu de atacat. Softul trebuie scris in acest mod, deoarece nu se poate anticipa cum pot avea loc viitoarele atacuri.

Nu trebuie sa ne bazam pe administratori, ca vor acoperi gaurile de securitate sau ca vor dezactiva anumite facilitati nefolosite. Acestia, cel mai probabil, nu o vor face sau nu vor sti sa o faca, sau, cel mai adesea, sunt foarte ocupati incat nu vor avea timp. In privinta utilizatorilor obisnuiti (neinformaticieni), acestia nu vor sti sa regleze problemele de securitate sau sa dezactiveze facilitati.

In sfarsit, nu poti renunta la securitatea propriului produs in favoarea altcuiva. Vremurile in care securitatea era inteleasa doar de cativa au apus de mult; acum fiecare are datoria de livra produse sigure.










BIBLIOGRAFIE


1.


Michael Howard,

David LeBlanc

Writing Secure Code, Second Edition, Microsoft Press, 2004

2.


I. Smeureanu,

M. Dardala

Programarea in limbajul C/C , Ed. CISON, Bucuresti, 2004

3.


Joseph D. Sloan


Network Troubleshooting Tools, O'Reilly, 2001


4.

Michael Gregg

Inside Network Security Assessment : Guarding Your IT Infrastructure, SAMS, 2005

5.


Lorrie Faith Cranor,

Simson Garfinkel

Security and Usability, O'Reilly, 2005

6.


Andrew Williams


Buffer Overflow Attacks: Detect, Exploit, Prevent, Syngress, 2005

7.


Mark G. Graff,

Kenneth R. van Wyk

Secure Coding: Principles & Practices, O'Reilly, 2003

8.


Chris Adamson,

Joshua Marinacci

Swing Hacks, O'Reilly, 2005

9.

Steve Maguire

Writing solid code, Microsoft Press, 1993


10.

Allan Liska

The Practice of Network Security: Deployment Strategies for Production Environments, Prentice Hall, 2002

11.



T. J. Klevinsky,

Scott Laliberte,

Ajay Gupta

Hack I.T.: Security Through Penetration Testing, Addison Wesley, 2002

12.

Chris McNab

Network Security Assessment, O'Reilly, 2004


13.


Adam Freeman,

Allen Jones

Programming .NET Security, O'Reilly, 2003

14.


Anton Chuvakin,

Cyrus Peikari

Security Warrior, O'Reilly, 2004

15.


Tara Calishain,

Kevin Hemenway

Spidering Hacks, O'Reilly, 2003

16.


Man Young Rhee.


Internet Security, Willey, 2003


17.



Stuart McClure,

Saumil Shah,

Shreeraj Shah

Web Hacking: Attacks and Defense,  Addison Wesley, 2002


18.

Jesse Liberty

Programming C#, 3rd Edition, O'Reilly, 2003


19.

Bruce Eckel

Thinking in C++, Prentice Hall, 2000



Nu se poate descarca referatul
Acest document nu se poate descarca

E posibil sa te intereseze alte documente despre:


Copyright © 2024 - Toate drepturile rezervate QReferat.com Folositi documentele afisate ca sursa de inspiratie. Va recomandam sa nu copiati textul, ci sa compuneti propriul document pe baza informatiilor de pe site.
{ Home } { Contact } { Termeni si conditii }