Aktuálna situácia: PHP2Shell. Dve kritické zraniteľnosti PHP (CVSS 9.2) umožňujú spustenie škodlivého kódu bez prihlásenia. Záplaty od 21. 10. 2026.Čo treba urobiť teraz →

Type Juggling v PHP: voľné porovnania ako bezpečnostné riziko

Prečo môžu voľné porovnania pomocou == v PHP viesť k bezpečnostným zraniteľnostiam, čo sa zmenilo s PHP 8.0 a ako napíšete porovnania v prihlásení, tokenoch a oprávneniach spoľahlivo.

Stav 5 min čítaniaBezpečný vývoj

Stručne: PHP Type Juggling označuje automatický prevod typov, ktorý pri voľnom porovnaní pomocou == rozhoduje o výsledku. Od PHP 8.0 sú pravidlá prísnejšie, ale číselné reťazce sa naďalej porovnávajú ako čísla a true, false a null sa riadia vlastnými pravidlami. Pre porovnania dôležité z hľadiska bezpečnosti preto platí: ===, in_array(..., true), match namiesto switch, hash_equals() pre tokeny a password_verify() pre heslá.

Porovnanie, ktoré je pravdivé, hoci by nemalo byť

Mnohé bezpečnostné zraniteľnosti v aplikáciách PHP nesedia v komplikovaných funkciách, ale v jedinom znaku: v ==, kde patrilo ===. Kód vyzerá správne, testy s bežnými vstupmi prejdú a napriek tomu kontrola za určitých podmienok prepustí niečo, čo mala zastaviť.

Príčinou je vlastnosť PHP, ktorá je v každodennom živote praktická: hodnoty sa podľa potreby prevádzajú na iný typ. Reťazec z formulára sa tak dá bez okľuky porovnať s číslom z databázy. Pri prihláseniach, tokenoch, rolách alebo stavových poliach môže byť práve táto veľkorysosť nebezpečná.

Ako PHP porovnáva pomocou ==

Príručka PHP podrobne opisuje pravidlá voľných porovnaní. Pre bezpečnosť sú dôležité najmä tieto prípady:

Porovnanie PHP 7 PHP 8 Dôvod
0 == "a" pravda nepravda od PHP 8.0 sa porovnáva ako reťazce
"1" == "01" pravda pravda obe číselné, porovnanie ako číslo
"10" == "1e1" pravda pravda exponenciálny zápis je číselný
100 == "1e2" pravda pravda číslo voči číselnému reťazcu
null == false pravda pravda porovnanie ako pravdivostná hodnota
"abc" == true pravda pravda neprázdny reťazec platí ako pravda

Zmena v PHP 8.0 zmiernila celú triedu chýb: predtým sa nečíselný reťazec pri porovnaní s číslom stal 0. Týkalo sa to aj príkazov switch. Ostatné riadky tabuľky však ukazujú, že voľné porovnania naďalej skrývajú prekvapenia.

Aj operátory <, > a porovnávací operátor <=> sa riadia týmito pravidlami. Pri triedení je to zvyčajne neškodné, pri kontrolách ako „suma menšia než limit“ s hodnotami z požiadavky by mal byť typ vopred určený.

Obzvlášť zákerný je riadok s exponenciálnym zápisom. Hodnoty hashov a tokeny často pozostávajú z číslic a písmen. Ak taká hodnota náhodou vyzerá ako číslo v exponenciálnom zápise, == môže dva rôzne hashe porovnať ako rovnaké. To je pozadie mnohých správ o obchádzaní prihlásenia v starších aplikáciách PHP.

PHP Type Juggling v typických bezpečnostných kontrolách

Voľné porovnania sa skrývajú na viacerých miestach než len v operátore ==:

  1. in_array() a array_search() porovnávajú bez tretieho parametra voľne. Zoznam povolených rolí alebo ID sa tým stáva mäkším, než sa čaká.
  2. switch porovnáva voľne. Výraz match dostupný od PHP 8.0 naopak podľa príručky porovnáva typovo bezpečne pomocou === a vyhodí chybu, ak nesedí žiadna vetva.
  3. Vstupy JSON dodávajú skutočné typy. Pole, ktoré očakávate ako reťazec, môže prísť ako true alebo ako číslo a pri == sa prekvapivo zhodovať.
  4. strcmp() pre tajomstvá je nevhodná, pretože čas behu závisí od obsahu a chybové prípady sa ľahko vyhodnotia nesprávne.
  5. Kontrola návratových hodnôt: funkcie ako strpos() vracajú 0 pre zhodu na začiatku a false pre žiadnu zhodu. if (!strpos(...)) oboje zamieňa.

Bezpečné porovnania: porovnanie vedľa seba

Nasledujúce príklady ukazujú vždy nebezpečný a bezpečný variant:

<?php
declare(strict_types=1);

// Skontrolovať rolu
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nebezpečné: voľne
in_array($rolle, $erlaubteRollen, true);    // bezpečné: striktne

// Vyhodnotiť stav
$aktion = match ($status) {                 // striktne, chyba pri neznámej hodnote
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Skontrolovať token z odkazu
if ($tokenAusLink == $gespeichertesToken) { /* nebezpečné */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* bezpečné */ }

// Skontrolovať heslo
if (md5($eingabe) == $hash) { /* nebezpečné: slabý algoritmus a voľné porovnanie */ }
if (password_verify($eingabe, $hash)) { /* bezpečné */ }

// Hľadať podreťazec
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

hash_equals() očakáva podľa príručky ako prvý parameter známe tajomstvo a ako druhý hodnotu používateľa a porovnáva v konštantnom čase. Ako tokeny vôbec bezpečne vytvárať, opisuje Bezpečné náhodné čísla a tokeny v PHP.

Dôležité

declare(strict_types=1) porovnania nerobí striktnými. Príkaz sa týka len prevodu typov pri volaniach funkcií a návratových hodnotách. Voľné porovnania musíte v kóde nahradiť sami.

Striktné porovnania majú aj druhú stranu: zlyhajú, keď typy nesedia. '42' === 42 je nepravda. Hodnoty z formulárov a URL prichádzajú v PHP ako reťazce, hodnoty z databázy podľa ovládača a nastavenia ako reťazce alebo čísla. Kto všade zavedie === bez zosúladenia typov, vytvorí chyby, ktoré sa v prevádzke prejavia ako odmietnutie oprávnených používateľov.

Riešením je jasná hranica: vstupy sa pri prijatí validujú a uvedú na správny typ, napríklad pomocou filter_var($wert, FILTER_VALIDATE_INT), a potom sa v kóde spracúvajú už len s deklaráciami typov a striktnými porovnaniami. Deklarácie typov pri parametroch, návratových hodnotách a vlastnostiach tomu pomáhajú, pretože nesprávne typy potom vyjdú najavo včas s chybovým hlásením, namiesto toho, aby potichu zmizli v porovnaní.

Systematická kontrola existujúceho kódu

V prerastených projektoch nájdete voľné porovnania v stovkách. Nie každé je problém. Sústreďte sa na miesta, kde sa rozhoduje o prístupe:

# Voľné porovnania v riadkoch s bezpečnostne relevantnými pojmami
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array bez striktného porovnania
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Nástroje statickej analýzy ako PHPStan alebo Psalm môžu na požiadanie hlásiť voľné porovnania trvalo. Nové prípady sa tak vôbec nedostanú do hlavnej vetvy. Základy o typoch opisuje Typový systém PHP; ako sa vstupy vopred uvedú na správny typ, ukazuje Validácia a filtrovanie vstupov v PHP.

Čo to znamená pre staré systémy

Aplikácie, ktoré ešte bežia na PHP 7, sú dotknuté starými, voľnejšími pravidlami porovnávania. K tomu pristupuje druhý problém: pre PHP 8.1 a staršie už projekt PHP neposkytuje oficiálne bezpečnostné aktualizácie, ani proti PHP2Shell. Situáciu opisuje PHP 8.1 a staršie: čo platí teraz.

Pri migrácii na PHP 8 sa niektoré výsledky porovnaní menia. Je to zvyčajne zlepšenie, ale môže to pokaziť kód, ktorý sa spoliehal na staré správanie. Naplánujte preto testy, ktoré cielene pokrývajú porovnania s číslami, prázdnymi hodnotami a null.

Ako konkrétne pomáha PHP SOS?

  • Prehľadáme váš kód na voľné porovnania na miestach, ktoré rozhodujú o prihlásení, právach alebo platbách.
  • Nahradíme ich striktnými porovnaniami, výrazmi match a vhodnými kontrolnými funkciami.
  • Nastavíme statickú analýzu tak, aby sa nové voľné porovnania odhalili v buildu.
  • Migrácie na aktuálne verzie PHP sprevádzame testami pre zmenené pravidlá porovnávania.

Kontrolu vášho softvéru opisuje Bezpečnostný audit a penetračný test.

Časté otázky

Čo je Type Juggling v PHP?

PHP pri niektorých operáciách automaticky prevádza hodnoty rôznych typov, napríklad reťazec na číslo. Pri voľnom porovnaní pomocou == o výsledku rozhoduje práve tento prevod. Je to pohodlné, ale môže viesť k porovnaniam, ktoré sú pravdivé, hoci hodnoty nevyzerajú rovnako.

Vyriešilo PHP 8 tento problém?

Čiastočne. Od PHP 8.0 sa číslo s nečíselným reťazcom porovnáva ako reťazec, takže 0 == 'a' už nie je pravda. Dva číselné reťazce sa však naďalej porovnávajú ako čísla a porovnania s true, false a null sa riadia vlastnými pravidlami.

Pomáha declare(strict_types=1) proti voľným porovnaniam?

Nie. strict_types riadi, či sa skalárne typy pri volaniach funkcií a návratoch automaticky prevádzajú. Na porovnávacie operátory == a != nemá vplyv.

Ako správne porovnávať heslá a tokeny?

Heslá overujte pomocou password_verify, tokeny a podpisy pomocou hash_equals. Obe porovnávajú presne a hash_equals v konštantnom čase. Porovnanie pomocou == alebo strcmp je na to nevhodné.

Zdroje

Okamžitý kontakt

Každá hodina sa počíta. Napíšte nám hneď.

Stručne opíšte systém, verziu PHP a svoje pozorovanie. Ozveme sa, vyjasníme situáciu a budeme pracovať na diaľku, kým nebude incident uzavretý a zdokumentovaný.

Okamžitá pomoc