Situația actuală: PHP2Shell. Două vulnerabilități critice PHP (CVSS 9.2) permit cod malițios fără autentificare. Patchuri din 21.10.2026.Ce este de făcut acum →

Type Juggling în PHP: comparațiile lejere ca risc de securitate

De ce comparațiile lejere cu == în PHP pot duce la vulnerabilități, ce s-a schimbat cu PHP 8.0 și cum scrieți robust comparațiile în autentificări, token-uri și permisiuni.

Stadiu 6 min de cititDezvoltare sigură

Pe scurt: PHP Type Juggling desemnează conversia automată de tip care, la comparația lejeră cu ==, decide rezultatul. Începând cu PHP 8.0 regulile sunt mai stricte, dar șirurile numerice sunt comparate în continuare ca numere, iar true, false și null urmează reguli proprii. Pentru verificările relevante pentru securitate se aplică de aceea: ===, in_array(..., true), match în loc de switch, hash_equals() pentru token-uri și password_verify() pentru parole.

O comparație care este adevărată, deși n-ar trebui să fie

Multe vulnerabilități din aplicațiile PHP nu se află în funcții complicate, ci într-un singur caracter: un == acolo unde ar fi trebuit un ===. Codul pare corect, testele cu introduceri normale trec și, totuși, în anumite condiții verificarea lasă să treacă ceva ce ar fi trebuit să oprească.

Cauza este o proprietate a PHP, practică în viața de zi cu zi: valorile sunt convertite la nevoie într-un alt tip. Un șir de caractere dintr-un formular poate fi astfel comparat fără ocol cu un număr din baza de date. La autentificări, token-uri, roluri sau câmpuri de stare, exact această generozitate poate deveni periculoasă.

Cum compară PHP cu ==

Manualul PHP descrie detaliat regulile pentru comparațiile lejere. Pentru securitate sunt importante mai ales aceste cazuri:

Comparație PHP 7 PHP 8 Motiv
0 == "a" adevărat fals comparate ca șiruri începând cu PHP 8.0
"1" == "01" adevărat adevărat ambele numerice, comparație ca număr
"10" == "1e1" adevărat adevărat notația exponențială este numerică
100 == "1e2" adevărat adevărat număr față de șir numeric
null == false adevărat adevărat comparație ca valoare booleană
"abc" == true adevărat adevărat un șir nevid este considerat adevărat

Modificarea din PHP 8.0 a dezamorsat o întreagă clasă de erori: înainte, un șir nenumeric devenea 0 la compararea cu un număr. Aceasta privea și instrucțiunile switch. Celelalte rânduri ale tabelului arată însă că comparațiile lejere ascund în continuare surprize.

Și operatorii <, > și operatorul de comparație <=> urmează aceste reguli. La sortări este de obicei inofensiv, dar la verificări precum „suma mai mică decât limita”, cu valori din cerere, tipul ar trebui să fie stabilit dinainte.

Deosebit de perfid este rândul cu notația exponențială. Valorile hash și token-urile constau adesea din cifre și litere. Dacă o astfel de valoare arată întâmplător ca un număr în notație exponențială, == poate compara două hash-uri diferite drept egale. Acesta este fundalul multor rapoarte despre ocoliri ale autentificării în aplicații PHP mai vechi.

PHP Type Juggling în verificări tipice de securitate

Comparațiile lejere se ascund în mai multe locuri decât doar în operatorul ==:

  1. in_array() și array_search() compară lejer fără al treilea parametru. O listă de permisiuni cu roluri sau ID-uri devine astfel mai moale decât se crede.
  2. switch compară lejer. Expresia match, disponibilă începând cu PHP 8.0, compară în schimb, potrivit manualului, sigur din punct de vedere al tipului, cu ===, și aruncă o eroare dacă nu se potrivește nicio ramură.
  3. Intrările JSON furnizează tipuri reale. Un câmp pe care îl așteptați ca șir poate sosi ca true sau ca număr și poate coincide surprinzător cu ==.
  4. strcmp() pentru secrete este nepotrivită, deoarece durata de execuție depinde de conținut, iar cazurile de eroare sunt ușor evaluate greșit.
  5. Verificarea valorilor returnate: funcții precum strpos() returnează 0 pentru o potrivire la început și false pentru nicio potrivire. if (!strpos(...)) le confundă pe amândouă.

Comparații sigure: confruntarea

Exemplele următoare arată de fiecare dată varianta nesigură și pe cea sigură:

<?php
declare(strict_types=1);

// Verificarea rolului
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nesigur: lejer
in_array($rolle, $erlaubteRollen, true);    // sigur: strict

// Evaluarea stării
$aktion = match ($status) {                 // strict, eroare la valoare necunoscută
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Verificarea token-ului din link
if ($tokenAusLink == $gespeichertesToken) { /* nesigur */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* sigur */ }

// Verificarea parolei
if (md5($eingabe) == $hash) { /* nesigur: procedeu slab și comparație lejeră */ }
if (password_verify($eingabe, $hash)) { /* sigur */ }

// Căutarea unui subșir
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

hash_equals() așteaptă, potrivit manualului, secretul cunoscut ca prim parametru și valoarea utilizatorului ca al doilea și compară în timp constant. Cum generați în general token-uri în siguranță descrie Numere aleatorii și token-uri sigure în PHP.

Important

declare(strict_types=1) nu face comparațiile stricte. Instrucțiunea privește doar conversia de tip la apelurile de funcții și la valorile returnate. Comparațiile lejere trebuie să le înlocuiți singuri în cod.

Comparațiile stricte au o față ascunsă: eșuează dacă tipurile nu se potrivesc. '42' === 42 este fals. Valorile din formulare și URL-uri sosesc în PHP ca șiruri, valorile din baza de date, în funcție de driver și setări, ca șiruri sau numere. Cine introduce peste tot ===, fără a aduce tipurile la același nivel, produce erori care se observă în exploatare ca respingerea unor utilizatori îndreptățiți.

Soluția este o graniță clară: intrările sunt validate la primire și aduse la tipul corect, de exemplu cu filter_var($wert, FILTER_VALIDATE_INT), iar apoi în cod sunt tratate doar cu declarații de tip și comparații stricte. Declarațiile de tip la parametri, la valori returnate și la proprietăți ajută în acest sens, deoarece tipurile greșite sunt observate devreme printr-un mesaj de eroare, în loc să dispară pe tăcute într-o comparație.

Verificarea sistematică a codului existent

În proiectele crescute în timp se găsesc sute de comparații lejere. Nu fiecare este o problemă. Concentrați-vă pe locurile în care se ia o decizie privind accesul:

# Comparații lejere în rânduri cu termeni relevanți pentru securitate
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array fără comparație strictă
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Instrumentele de analiză statică precum PHPStan sau Psalm pot raporta, la cerere, permanent comparațiile lejere. Astfel cazurile noi nu ajung deloc în ramura principală. Bazele despre tipuri le descrie Sistemul de tipuri al PHP; cum sunt aduse intrările în prealabil la tipul corect arată Validarea și filtrarea intrărilor în PHP.

Ce înseamnă aceasta pentru sistemele vechi

Aplicațiile care rulează încă pe PHP 7 sunt afectate de vechile reguli de comparație, mai laxe. La aceasta se adaugă o a doua problemă: pentru PHP 8.1 și mai vechi nu mai există din partea proiectului PHP actualizări oficiale de securitate, nici împotriva PHP2Shell. Situația o descrie PHP 8.1 și mai vechi: ce se aplică acum.

La o migrare spre PHP 8, unele rezultate ale comparațiilor se schimbă. Aceasta este de regulă o îmbunătățire, dar poate strica cod care se baza pe vechiul comportament. Planificați de aceea teste care acoperă în mod țintit comparațiile cu numere, valori goale și null.

Cum ajută PHP SOS concret?

  • Căutăm în codul dumneavoastră comparații lejere în locurile care decid asupra autentificării, drepturilor sau plăților.
  • Le înlocuim cu comparații stricte, expresii match și funcții de verificare potrivite.
  • Configurăm analiza statică astfel încât comparațiile lejere noi să iasă în evidență în build.
  • Însoțim migrările către versiuni PHP actuale cu teste pentru regulile de comparație modificate.

O verificare a software-ului dumneavoastră descrie Audit de securitate și test de penetrare.

Întrebări frecvente

Ce este Type Juggling în PHP?

PHP convertește automat valori de tipuri diferite la unele operații, de exemplu un șir de caractere într-un număr. La comparația lejeră cu ==, această conversie decide rezultatul. Este comod, dar poate duce la comparații adevărate, deși valorile nu arată egale.

A rezolvat PHP 8 problema?

Parțial. Începând cu PHP 8.0, un număr este comparat cu un șir nenumeric ca șir, deci 0 == 'a' nu mai este adevărat. Două șiruri numerice sunt totuși comparate în continuare ca numere, iar comparațiile cu true, false și null urmează reguli proprii.

Ajută declare(strict_types=1) împotriva comparațiilor lejere?

Nu. strict_types controlează dacă tipurile scalare sunt convertite automat la apelurile de funcții și la valorile returnate. Nu are nicio influență asupra operatorilor de comparație == și !=.

Cum compar corect parolele și token-urile?

Parolele le verificați cu password_verify, token-urile și semnăturile cu hash_equals. Ambele compară exact și, în cazul hash_equals, în timp constant. O comparație cu == sau strcmp este nepotrivită pentru aceasta.

Surse

Contact imediat

Fiecare oră contează. Scrieți-ne acum.

Descrieți pe scurt sistemul, versiunea PHP și ce ați observat. Ne vom face auziți, clarificăm situația și lucrăm la distanță până când incidentul este încheiat și documentat.

Ajutor imediat