Pe scurt: Validați intrările în PHP cu filter_var() sau filter_input_array() și cu reguli clare pentru fiecare câmp: tip, interval, lungime și valori permise. Ce nu se potrivește este respins, nu transformat pe tăcute. Validarea este importantă pentru date curate și o suprafață de atac mai mică, dar nu înlocuiește nici prepared statements împotriva SQL injection, nici codarea adecvată contextului împotriva XSS.
De ce „filtrează totul” nu este un concept
În multe proiecte PHP mai vechi se găsește la începutul fiecărui fișier o buclă care trece toate valorile din $_GET și $_POST printr-o funcție de curățare. Aceasta dă o senzație de siguranță. În practică, elimină uneori prea mult, de exemplu apostrofurile din nume de familie, și alteori prea puțin, pentru că aceeași funcție ar trebui să fie valabilă simultan pentru SQL, HTML și nume de fișiere.
O validare rezonabilă lucrează altfel: se întreabă pentru fiecare câmp ce este permis acolo. O cantitate comandată este un număr întreg între 1 și o limită superioară. Un câmp de sortare este una dintre trei valori fixe. O dată are un anumit format și se află înainte sau după o altă dată. Orice altceva este respins.
OWASP distinge aici două niveluri: verificarea sintactică, dacă o valoare are forma corectă, și verificarea semantică, dacă are sens în context. Ambele aparțin serverului; verificările din browser servesc doar comodității de utilizare.
Ce filtre oferă PHP
Extensia de filtre este inclusă în PHP în mod implicit. Cele mai importante filtre de validare conform manualului PHP:
| Filtru | Verifică | Opțiuni și observații importante |
|---|---|---|
FILTER_VALIDATE_INT |
număr întreg | min_range, max_range |
FILTER_VALIDATE_FLOAT |
număr zecimal | decimal pentru separator |
FILTER_VALIDATE_BOOL |
valoare booleană | cu FILTER_NULL_ON_FAILURE se poate deosebi de „fals” |
FILTER_VALIDATE_EMAIL |
forma unei adrese de e-mail | nu dovedește că poșta există |
FILTER_VALIDATE_URL |
forma unei URL | verificați singuri schema, fără domenii internaționalizate |
FILTER_VALIDATE_IP |
adresă IP | flaguri pentru intervale private și rezervate |
FILTER_VALIDATE_REGEXP |
tipar propriu | ancorați tiparul cu ^ și $ |
Filtrul de curățare FILTER_SANITIZE_STRING este depășit începând cu PHP 8.1. Cine îl mai folosește ar trebui să treacă validarea la filtre potrivite și să folosească htmlspecialchars() la ieșire.
filter_var în PHP: verificarea curată a unui formular
Cu filter_input_array() se poate verifica un întreg formular într-un singur pas, pe baza unei definiții. Câmpurile necunoscute nu sunt preluate:
<?php
declare(strict_types=1);
$regeln = [
'menge' => ['filter' => FILTER_VALIDATE_INT,
'options' => ['min_range' => 1, 'max_range' => 50]],
'email' => FILTER_VALIDATE_EMAIL,
'agb' => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
'versand' => ['filter' => FILTER_VALIDATE_REGEXP,
'options' => ['regexp' => '/^(standard|express)$/']],
];
$daten = filter_input_array(INPUT_POST, $regeln) ?? [];
$fehler = [];
foreach ($regeln as $feld => $_) {
if (!array_key_exists($feld, $daten) || $daten[$feld] === false || $daten[$feld] === null) {
$fehler[] = $feld;
}
}
if ($fehler !== []) {
http_response_code(422);
// Afișați mesajul de eroare pentru fiecare câmp, nu procesați intrările
}
Rețineți comparațiile stricte cu ===: o valoare validă 0 sau un șir gol nu are voie să fie confundată cu un eșec. De ce comparațiile lejere sunt periculoase aici explică Type Juggling: comparațiile lejere ca risc de securitate. Cum pune PHP, în general, la dispoziție datele cererii descrie Superglobals: $_GET, $_POST, $_FILES și celelalte.
Capcane la e-mail, URL și texte libere
Unele filtre fac mai puțin decât sugerează numele lor:
- URL-uri:
FILTER_VALIDATE_URLacceptă și alte scheme decât http și https. Verificați apoi schema cuparse_url()față de o listă fixă. Dacă URL-ul este apelat de server, se aplică reguli suplimentare, vedeți Prevenirea Server-Side Request Forgery în PHP. - Adrese de e-mail: Filtrul verifică forma, nu proprietatea. Cine trebuie să fie sigur trimite un link de confirmare.
- Text liber: Numele, mesajele și adresele pot fi greu limitate în mod rezonabil la anumite caractere. Verificați lungimea și codarea caracterelor, de exemplu cu
mb_check_encoding($wert, 'UTF-8'), și bazați-vă la ieșire pe codare. - Expresii regulate: Fără
^și$, un tipar verifică doar dacă apare undeva în text, nu dacă se potrivește întreaga valoare. - Array-uri în loc de șiruri: Un atacator poate trimite un array în loc de un text. Fără verificarea tipului, aceasta duce la avertismente sau la comportament neașteptat.
Pentru valori de selecție fixe, precum sortarea, limba sau modul de expediere, o listă de permisiuni cu in_array($wert, $erlaubt, true) sau cu o expresie match este mai simplă și mai strictă decât orice expresie regulată.
O altă greșeală frecventă privește numerele cu semnificație, de exemplu prețurile, cantitățile sau reducerile. Faptul că o valoare este un număr valid nu spune nimic despre dacă este permisă în context. O cantitate negativă, o reducere peste valoarea mărfii sau un preț preluat din formular în loc de baza de date sunt erori clasice de logică. Prețurile, permisiunile și valorile de stare aparțin de aceea întotdeauna serverului și niciodată unor câmpuri ascunse de formular.
JSON, webhook-uri și importuri: validarea dincolo de formulare
Multe intrări nu mai sunt astăzi formulare, ci cereri JSON de la aplicații, webhook-uri de la servicii de plată sau fișiere de import de la furnizori. Și aici se aplică: mai întâi citire sigură, apoi verificare. OWASP sfătuiește să limitați dimensiunea și adâncimea de imbricare înainte ca datele să fie citite. În PHP, json_decode() cu o adâncime maximă și JSON_THROW_ON_ERROR preia o parte din aceasta:
<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // cel mult 64 KB
try {
$daten = json_decode($roh, true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException) {
http_response_code(400);
exit;
}
if (!is_array($daten) || !is_string($daten['bestellnummer'] ?? null)) {
http_response_code(422);
exit;
}
La webhook-uri, înaintea verificării conținutului se pune întrebarea dacă mesajul provine într-adevăr de la serviciul indicat. Dacă serviciul oferă o semnătură, verificați-o și comparați-o cu hash_equals(). La fișierele de import, verificați fiecare rând ca pe un formular și opriți-vă curat la erori, în loc să preluați înregistrări pe jumătate.
Mesajele de eroare ar trebui să numească câmpul afectat, dar să nu returneze nefiltrată valoarea respinsă. Altfel apare chiar la mesajul de eroare un nou punct de ieșire, care trebuie codat din nou.
Ce nu poate realiza validarea
Validarea reduce suprafața de atac, dar, potrivit OWASP, nu este apărarea principală împotriva injection. Un nume de familie cu apostrof este valid și trebuie totuși să ajungă în siguranță într-o bază de date. Aceasta reușește doar cu prepared statements, așa cum arată ghidul Prevenirea SQL injection în PHP. Iar un text de comentariu valid devine inofensiv abia prin codarea adecvată contextului la ieșire.
Ordinea corectă este deci: validați la primire, parametrizați la procesare, codați la ieșire. În plus, înaintea lor trebuie să stea limitele de dimensiune: cererile mult prea mari nu ar trebui să fie procesate integral nici de serverul web, nici de PHP. Servesc pentru aceasta setări precum post_max_size, care oricum ar trebui verificate după PHP2Shell.
Cum ajută PHP SOS concret?
- Inventariem toate intrările aplicației dumneavoastră: formulare, interfețe, upload-uri, importuri și webhook-uri.
- Stabilim pentru fiecare câmp ce este permis și înlocuim funcțiile globale de curățare cu verificări clare.
- Verificăm dacă accesul la baza de date și ieșirile sunt securizate independent de validare.
- Documentăm regulile astfel încât echipa dumneavoastră să le poată continua la funcții noi.
O verificare sistematică descrie serviciul Audit de securitate și test de penetrare.
Întrebări frecvente
Care este diferența dintre validare și curățare?
Validarea verifică dacă o valoare corespunde așteptărilor și, altfel, o respinge. Curățarea modifică valoarea, de exemplu prin eliminarea unor caractere. Pentru deciziile de securitate, respingerea este aproape întotdeauna mai bună, deoarece o valoare modificată pe tăcute poate produce erori noi.
Mai este utilizabil FILTER_SANITIZE_STRING?
Filtrul este marcat ca depășit începând cu PHP 8.1. Manualul PHP recomandă în schimb htmlspecialchars() la ieșire. Ar trebui să convertiți codul existent, deoarece funcțiile depășite pot dispărea în versiuni viitoare.
Este suficient FILTER_VALIDATE_URL pentru a face linkurile sigure?
Nu. Filtrul verifică doar forma adresei. Potrivit manualului, o URL validă nu trebuie să înceapă cu http, de aceea trebuie să verificați singuri schema față de o listă de permisiuni, precum https. Numele de domeniu internaționalizate sunt respinse de filtru.
Trebuie să validez și datele din propria bază de date?
La ieșire, ele sunt considerate nesigure și sunt codate corespunzător. O nouă validare este utilă dacă datele au venit inițial din exterior și sunt acum folosite într-un context nou, de exemplu ca nume de fișier sau parte dintr-o comandă.
Ajută validarea împotriva PHP2Shell?
Nu. PHP2Shell privește procesarea cererilor în interpretorul PHP, înainte ca să ruleze măcar codul aplicației dumneavoastră. Împotriva ei ajută doar actualizarea PHP, urmată de repornirea tuturor proceselor PHP.