# Type Juggling v PHP: voľné porovnania ako bezpečnostné riziko

> Pochopte PHP Type Juggling: ako porovnáva ==, čo zmenilo PHP 8.0 a prečo sa tokeny, hashe a roly musia porovnávať striktne.

Zdroj: https://phpsos.de/sk/blog/type-juggling-php/  
Stav: 25.10.2026  
Poskytovateľ: PHP SOS, Núdzová pomoc pri PHP a bezpečnosť serverov. Okamžitá pomoc cez WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20de%C5%88%2C%20PHP%20SOS%2C%20potrebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20serverom%20s%20PHP.%20Spolo%C4%8Dnos%C5%A5%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**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
<?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](https://phpsos.de/sk/blog/sichere-tokens-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:

```bash
# 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](https://phpsos.de/sk/php/typsystem/); ako sa vstupy vopred uvedú na správny typ, ukazuje [Validácia a filtrovanie vstupov v PHP](https://phpsos.de/sk/blog/eingaben-validieren-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](https://phpsos.de/sk/php2shell/php-8-1-und-aelter/).

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](https://phpsos.de/sk/leistungen/sicherheitsaudit-penetrationstest/).


## Č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

- [Príručka PHP: porovnávacie operátory](https://www.php.net/manual/de/language.operators.comparison.php)
- [Príručka PHP: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
