Responsabil laborator: Andrei Avram (andrei.avram@gmail.com)

Data postării laboratorului: Vineri, 10 Aprilie, 11:34 AM

Data ultimei modificări: Vineri, 12 aprilie, 3:59 PM


Sesiuni și securitate în PHP


  1. Mecanismul de sesiuni al PHP, funcția session_start(), superglobala $_SESSION 
  2. Utilizarea sesiunilor pentru implementarea autentificării 
  3. Principii de securitate în PHP 
  4. Atacuri de tip SQL Injection și protecție împotriva lui 


Mecanismul de sesiuni în PHP

După cum s-a mai discutat la laborator, HTTP este un protocol state-less. Cea mai importantă urmare a acestui fapt din punctul de vedere al aplicațiilor web fiind că, folosind doar protocolul HTTP, un server web nu poate determina dacă o cerere este legată logic de una anterioară, de la același client.


Capacitatea serverului web de a recunoaște că o cerere face parte dintr-o succesiune de cereri de la același client este utilă, spre exemplu, pentru implementarea unei zone protejate cu parolă în aplicație ( caz în care utilizatorul se autentifică odată, apoi toate cererile de la acest client sunt considerate autentificate până la logout).


Pentru a implementa un asemenea comportament, se utilizează mai multe metode aplicate peste nivelul HTTP al server-ului web, în funcție de tipul de server și de limbajul de programare. În PHP, cel mai utilizat mecanism pentru reținerea informațiilor despre starea clientului este sesiunea (http://www.php.net/session).


Mecanismul pentru sesiuni este foarte simplu:

  1. Un script PHP inițiază o sesiune cu session_start() 
  2. Se verifică cookie-urile trimise în REQUEST-ul HTTP sau linia de REQUEST a metodelor GET sau POST. 
    1. Dacă nu există un COOKIE cu numele default PHPSESSID (sau nu există paremtrul SID în linia REQUEST), se inițiază o sesiune nouă, care primește un session id aleator, și se creează în directorul temporar al sistemului un fișier în care vor fi păstrate informațiile sesiunii 
    2. Altfel, se utilizează valoarea parametrului (PHPSESSID sau SID din GET sau POST) pentru a porni sesiunea cu ACEL session id, care se presupune că există. 
  3. După ce sesiunea a fost pornită, utilizatorul are acces Read / Write la datele din fișierul temporar prin intermediul variabilei superglobale $_SESSION (care este un array). 
  4. Sesiunea se închide la expirarea unui timp sau prin funcția session_destroy() 


Prin utilizarea sesiunilor, limitarea duratei de viață a variabilelor la rularea unui script poate fi înlăturată. Valori utile pot fi salvate ca variabile de sesiune, și utilizate în orice script.


Folosirea sesiunilor pentru autentificare

Un caz de utilizare comun pentru sesiuni în programarea web (sesiunile nu sunt o tehnică limitată la PHP) este autentificarea și păstrarea în stare autentificată a unui utilizator al aplicației, în timpul navigării acestuia prin mai multe pagini web. Similar și oarecum cuprins în acest exemplu este și coșul de cumpărături dintr-un magazin virtual (în care se păstrează produsele cumpărate până într-un punct, chiar dacă utilizatorul vede mai multe pagini : detalii produs, comparație produse ...).


Pentru autentificarea unui utilizator, trebuie urmăriți următorii pași:

  1. Login form: utilizatorului ii se cere să întroducă informații într-un formular, care apoi este transmis scriptului de autentificare 
  2. Scriptul verifică informațiile de autentificare (spre exemplu, perechea user & password) și întoarce o decizie: informațiile sunt valide sau invalide. 
  3. În cazul în care informațiile sunt valide, în script se poate înregistra o variabilă de sesiune care să fie apoi folosită ca referință pentru starea autentificată. Aceasta este de cele mai multe ori un flag, sau o variabilă care conține numele de utilizator și care este ștearsă (cu unset) la logout, dar poate fi și alteceva, în funcție de complexitatea și cerințele aplicației. 

Înregistrarea unei variabile de sesiune se făcea în PHP4 cu session_register(), dar această funcție este deprecated acum. Modalitatea de înregistrare / deînregistrare a unei variabile de sesiune este creearea / ștergerea unei perechi cheie – valoare în array-ul superglobal $_SESSION.


Securitate în aplicații web. Securitate în PHP

Pentru o lucrare interesantă și utilă despre aspecte practice ale securității în PHP, consultați documentul acesta: http://phpsec.org/php-security-guide.pdf .

Aplicațiile web sunt mult mai expuse atacurilor decât aplicațiile convenționale, din mai multe motive. Unul dintre ele este că aplicația rulează centralizat, într-un singur loc, ceea ce prezintă o miză mai mare pentru atacatori (un eventual atac reușit poate să ofere acces la multe date ”prețioase”). Altul este că aplicațiile web se bazează pe browsere și pe protocolul HTTP, care s-au dovedit amândouă vulnerabile la atacuri.

Există mai multe tipuri de atacuri împotriva aplicațiilor web. Aproape toate atacurile pot fi respinse printr-un design corect al aplicației și, mai ales, o neîncredere totală în informațiile care vin în cererile HTTP.

Cererile HTTP sunt foarte ușor de interceptat și de modificat. Toți parametrii pentru aplicațiile web se transmit prin HTTP, deci toți parametrii trebuiesc validați ca și când ar veni dintr-o sursă nesigură. Faptul că PHP permite utilizarea folosirii îmbricate a ghilimelelor și alte syntax sugar-uri în PHP face ca începătorii să aibă posibilitate (și de multe ori tentația) de a scrie cod vulnerabil.

Un principiu de proiectare pentru securitatea în aplicații web este că se aplică o strategie de tipl white lists, nu black lists. Mai concret, la nivel conceptual, se decide că ”totul este interzis”, și apoi se stabilesc excepții (white list). Alternativa, adică ”totul e permis” și se specifică excepțiile, nu este scalabilă și trebuie luate în considerare toate cazurile particulare nepermise.

O primă regulă practică pentru securitate în PHP este filtrarea input-ului și verificarea că acesta are conținutul la care ne așteptăm. Input-ul în PHP vine în forma array-urilor superglobale $_GET și $_POST, și deci regula acesta se poate rescrie prin: conținul $_GET și $_POST nu se folosește decât DUPĂ verificare și filtrare.

Există două tipuri de verificare:

  1. Verificarea tipului conținutului: se fac verificări relativ la parametrul așteptat (spre exemplu dacă se ceruse un număr, și ce se primește nu e un număr), se verifică existența caracterelor speciale și se folosesc tehnici (cum ar fi escaping) pentru a face parametrul utilizabil. 
  2. Verificarea semanticii parametrului (o valoare poate fi, spre exemplu, un string valid, dar poate să nu însemne nimic în contextul curent al aplicației: spre exemplu se așteaptă true / false și se primește green). Faptul că un utilizator este limitat în browser să aleagă doar o anumită valoare dintr-un meniu de tip dropdown nu prezintă nici un fel de siguranță că pe serverside vor ajunge doar valorile din acel dropdown. 


Tipuri de atacuri

SQL Injection

SQL Injection este unu din cele mai comune tipuri de atacuri ale formularelor de autentificare. El se bazează pe faptul că un parametru obținut din cererea HTTP este folosit direct în query-ul PHP.

Exemplu

$query = “SELECT username, password FROM users WHERE username = '”.$_GET['user'].”' AND password = '$_GET['password']'”;
$result = $db->query($query);

Daca un utilizator încearcă un user existent, spre exemplu 'ion.popescu', cu parola aferentă acestui user, scriptul se execută și întoarce linia pentru acest utilizator, care apoi poate fi verificată și utilizatorul autentificat.

Daca, însă, un atacator da un user incorect, și în câmpul password din formularul HTML introduce

anything' OR 1 = 1

scriptul SQL care se rulează este

SELECT username, password FROM users WHERE username = 'utilizator_test' AND password = 'anything' OR 1 = 1

al cărui rezultat sunt toate rândurile din tabel, inclusiv cel pe care îl căutam noi. Dacă, spre exemplu, verificarea se facu cu $result->num_rows() > 0, un atacator obține acces în zona pentru utilizatori autentificați a aplicației noastre.

Protecția împotriva atacurilor de acest tip este, ca în cazul multor atacuri, filtrarea datelor. Parametrii care intră în alcătuirea unui query SQL trebuie să nu conțină caractere care sunt interpretate într-un mod deosebit de parser-ul de query-uri SQL. In acest sens, parametrii pot fi filtrați, sau, în cazul în care e totuși nevoie de caracterele respective, parametrii pot fi escape-uiți (encodați) folosind o schema acceptată și de baza de date.


Password hashing

Password hashing este o tehnică folosită pentru a proteja parola unui utilizator de oricine altcineva decât el, inclusiv de administratorii bazei de date cu utilizatori, dar și de orice persoane care urmăresc pachete pe rețea. O astfel de tehnică este utilă mai ales în contextul actual, în care un utilizator al Internet-ului are un număr mare de conturi, și folosește, pentru simplitate, aceeași parola în toate aceste conturi, aflarea ei având consecințe serioase pentru utilizatorul în cauză.

Password hashing inseamnă trecerea parolei printr-o funcție hash cunoscută ( o funcție hash are proprietatea că fiind dat un string, pentru acel string produce întotdeauna același rezultat la ieșire, de o dimensiune standard ), spre exemplu MD5, și apoi pasarea acestui rezultat pentru stocare în baza de date.

Verificarea parolei se poate face acum între hash-ul parolei introduse de utilizator și hash-ul stocat. Pentru a evita transmiterea parolei în clar pe HTTP, se poate folosi HTTPS (care este soluția preferată, dar mai scumpă atât ca timp cât și ca bani) sau se poate realiza hash-ul la client, folosind un client-side script.

Task-uri

  1. Implementarea unui mecanism de autentificare în PHP cu sesiuni (7p) 

Mai concret, implementați un mecanism complet prin care pagina member-zone.php să fie accesibilă doar membrilor autentificați, ceilalți useri (cei neautentificați) să fie redirectați automat către formularul de login. Se cere de asemenea existența unui link pentru logout, care să încheie sesiunea autentificată a unui utilizator.

Pagina cu login form-ul (loginForm.php) va afișa mesajele corespunzătoare de eroare, în cazul în care perechea utilizator – parolă nu este corectă.

Scriptul de autentificare se implementează în loginScript.php

  1. Protejarea scriptului de autentificare împotriva atacurilor de tip SQL Injection (2p) 

Găsiți un exemplu de atac SQL Injection care vă permite să vă autentificați în script-ul vostru fără a avea o pereche utilizator – parolă validă. Protejați toate cererile către baza de date, în special cele pentru verificarea perechii utilizator – parolă, de atacuri de tip SQL Injection.

  1. Implementarea unui mecanism de password hashing cu MD5 pentru protecția parolei (1p) 

Folosiți JavaScript pentru a calcula un hash MD5 a parolei înainte de trimitere, pentru a nu transmite în clar parola prin HTTP. Pentru calculul hash-ului MD5 se va folosi funcția hex_md5() din fișierul md5.js.

Pentru a testa, folosiți pentru autentificare funcția checkUserMD5() din clasa User, și folosiți membrul passwordHash în loc de membrul password.

Bonus:

  1. Implementarea unui script pentru autentificarea utilizatorilor în diferite grupuri cu permisiuni diferite (3p) 

Adăugați script-ului de până acum posibilitatea de a oferi acces utilizatorilor unui grup (numit admin) de a avea acces și la pagina admin-zone.php, dar fără ca ceilalți utilizatori (neautentificați sau care nu sunt în grupul admin) să aibă acces la această pagină.


Observații și hint-uri

Funcția header() din PHP permite modificarea headerelor trimise în raspunsul la cererea HTTP. Util pentru laboratorul 9 este în special header-ul Location. O secvență de forma

header(“Location: loginForm.php”);
exit();

va face browser-ul să deschidă pagina loginForm.php. Funcția header(), la fel ca și session_start(), nu pot fi folosite decât ÎNAINTE ca orice să fie fost scris către browser.

Notă: în contextul sesiunilor, exit() trebuie întotdeauna să urmeze un apel de tip header(”Location: ...”).

Modificarea unui câmp al unui formular în momentul trimiterii se face prin folosirea evenimentului onsubmit

<form name = 'form' id = 'form' onsubmit='functie_js()'>

Obținerea unei referințe către un formular se poate face cu getElementByID

document.getElementById('id_formular')

Obținerea unei referințe către valoarea elementului și modificarea ei se poate face cu

formular.id_camp.value = 'valoare_noua'