# Type juggling v PHP: volná porovnání jako bezpečnostní riziko

> Type juggling v PHP: jak porovnává ==, co změnilo PHP 8.0 a proč je nutné tokeny, hashe a role porovnávat striktně. S ukázkami kódu a kontrolou.

Zdroj: https://phpsos.de/cs/blog/type-juggling-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručně:** Type juggling v PHP označuje automatický převod typů, který u volného porovnání pomocí `==` rozhoduje o výsledku. Od PHP 8.0 jsou pravidla přísnější, ale numerické řetězce se dál porovnávají jako čísla a `true`, `false` a `null` se řídí vlastními pravidly. Pro bezpečnostně relevantní kontroly proto platí: `===`, `in_array(..., true)`, `match` místo `switch`, `hash_equals()` pro tokeny a `password_verify()` pro hesla.

## Porovnání, které platí, ačkoli by neplatit nemělo

Mnoho bezpečnostních mezer v aplikacích PHP se neskrývá ve složitých funkcích, ale v jediném znaku: v `==` tam, kam patřilo `===`. Kód vypadá správně, testy s běžnými vstupy projdou a kontrola přesto za určitých podmínek propustí něco, co měla zastavit.

Příčinou je vlastnost PHP, která je v běžném provozu praktická: hodnoty se podle potřeby převádějí na jiný typ. Řetězec z formuláře lze tak bez oklik porovnat s číslem z databáze. U přihlášení, tokenů, rolí nebo stavových polí se právě tato velkorysost může stát nebezpečnou.

## Jak PHP porovnává pomocí ==

Příručka PHP podrobně popisuje pravidla pro volná porovnání. Pro bezpečnost jsou důležité hlavně tyto případy:

| Porovnání | PHP 7 | PHP 8 | Důvod |
|---|---|---|---|
| `0 == "a"` | pravda | nepravda | od PHP 8.0 se porovnává jako řetězce |
| `"1" == "01"` | pravda | pravda | oba numerické, porovnání jako číslo |
| `"10" == "1e1"` | pravda | pravda | exponenciální zápis je numerický |
| `100 == "1e2"` | pravda | pravda | číslo proti numerickému řetězci |
| `null == false` | pravda | pravda | porovnání jako pravdivostní hodnota |
| `"abc" == true` | pravda | pravda | neprázdný řetězec platí jako pravda |

Změna v PHP 8.0 zmírnila celou třídu chyb: dříve se nenumerický řetězec při porovnání s číslem změnil na `0`. Týkalo se to i příkazů `switch`. Ostatní řádky tabulky ale ukazují, že volná porovnání dál skrývají překvapení.

Podle těchto pravidel se řídí i operátory `<`, `>` a operátor porovnání `<=>`. U řazení je to většinou neškodné, u kontrol typu „částka menší než limit“ s hodnotami z požadavku by měl být typ znám předem.

Obzvlášť záludný je řádek s exponenciálním zápisem. Hodnoty hashů a tokeny se často skládají z číslic a písmen. Pokud takto hodnota náhodou vypadá jako číslo v exponenciálním zápisu, `==` může dva různé hashe považovat za shodné. To je pozadí mnoha zpráv o obcházení přihlášení ve starších aplikacích PHP.

## Type juggling v PHP v typických bezpečnostních kontrolách

Volná porovnání se skrývají na více místech než jen v operátoru `==`:

1. **`in_array()` a `array_search()`** porovnávají bez třetího parametru volně. Seznam povolených rolí nebo ID je tak měkčí, než se zdá.
2. **`switch`** porovnává volně. Výraz `match`, dostupný od PHP 8.0, naproti tomu podle příručky porovnává typově bezpečně pomocí `===` a vyvolá chybu, pokud nesedí žádná větev.
3. **Vstupy JSON** poskytují skutečné typy. Pole, které očekáváte jako řetězec, může dorazit jako `true` nebo jako číslo a s `==` se překvapivě shodovat.
4. **`strcmp()` pro tajné hodnoty** se nehodí, protože doba běhu závisí na obsahu a chybové případy se snadno vyhodnotí špatně.
5. **Kontrola návratových hodnot**: funkce jako `strpos()` vracejí `0` pro nález na začátku a `false` pro žádný nález. `if (!strpos(...))` obojí zaměňuje.

## Bezpečná porovnání: srovnání

Následující příklady ukazují vždy nebezpečnou a bezpečnou variantu:

```php
<?php
declare(strict_types=1);

// Zkontrolovat roli
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nebezpečné: volně
in_array($rolle, $erlaubteRollen, true);    // bezpečné: striktně

// Vyhodnotit stav
$aktion = match ($status) {                 // striktně, chyba při neznámé hodnotě
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Zkontrolovat token z odkazu
if ($tokenAusLink == $gespeichertesToken) { /* nebezpečné */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* bezpečné */ }

// Zkontrolovat heslo
if (md5($eingabe) == $hash) { /* nebezpečné: slabá metoda a volné porovnání */ }
if (password_verify($eingabe, $hash)) { /* bezpečné */ }

// Hledat podřetězec
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }
```

`hash_equals()` očekává podle příručky jako první parametr známé tajemství a jako druhý hodnotu uživatele a porovnává v konstantním čase. Jak tokeny vůbec bezpečně vytvářet, popisuje článek [Bezpečná náhodná čísla a tokeny v PHP](https://phpsos.de/cs/blog/sichere-tokens-php/).

> **Důležité:**
> `declare(strict_types=1)` nečiní porovnání striktními. Příkaz se týká jen převodu typů u volání funkcí a návratových hodnot. Volná porovnání musíte v kódu nahradit sami.

Striktní porovnání mají druhou stranu: selhávají, když typy nesedí. `'42' === 42` je nepravda. Hodnoty z formulářů a URL přicházejí v PHP jako řetězce, hodnoty z databáze podle ovladače a nastavení jako řetězce nebo čísla. Kdo všude zavede `===`, aniž by typy sjednotil, vytvoří chyby, které se v provozu projeví jako odmítnutí oprávněných uživatelů.

Řešením je jasná hranice: vstupy se při příjmu validují a převedou na správný typ, například pomocí `filter_var($wert, FILTER_VALIDATE_INT)`, a poté se v kódu zpracovávají už jen s deklaracemi typů a striktními porovnáními. Deklarace typů u parametrů, návratových hodnot a vlastností k tomu pomáhají, protože chybné typy pak vyjdou brzy najevo s chybovým hlášením, místo aby tiše zmizely v porovnání.

## Systematická kontrola stávajícího kódu

V rozrostlých projektech se volná porovnání vyskytují po stovkách. Ne každé je problém. Soustřeďte se na místa, kde se rozhoduje o přístupu:

```bash
# Volná porovnání na řádcích s bezpečnostně relevantními pojmy
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array bez striktního porovnání
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
```

Nástroje statické analýzy jako PHPStan nebo Psalm mohou volná porovnání na požádání trvale hlásit. Nové případy se tak do hlavní větve vůbec nedostanou. Základy o typech popisuje [Typový systém PHP](https://phpsos.de/cs/php/typsystem/); jak se vstupy předem převedou na správný typ, ukazuje [Validace a filtrování vstupů v PHP](https://phpsos.de/cs/blog/eingaben-validieren-php/).

## Co to znamená pro starší systémy

Aplikace, které stále běží na PHP 7, jsou zasaženy starými, uvolněnějšími pravidly porovnávání. K tomu přistupuje druhý problém: pro PHP 8.1 a starší už projekt PHP nevydává oficiální bezpečnostní aktualizace, ani proti PHP2Shell. Situaci popisuje stránka [PHP 8.1 a starší: co platí nyní](https://phpsos.de/cs/php2shell/php-8-1-und-aelter/).

Při migraci na PHP 8 se některé výsledky porovnání mění. Je to většinou zlepšení, ale může to rozbít kód, který se spoléhal na staré chování. Počítejte proto s testy, které cíleně pokrývají porovnání s čísly, prázdnými hodnotami a `null`.

## Jak konkrétně pomáhá PHP SOS?

- Prohledáme váš kód na volná porovnání v místech, kde se rozhoduje o přihlášení, právech nebo platbách.
- Nahradíme je striktními porovnáními, výrazy `match` a vhodnými kontrolními funkcemi.
- Nastavíme statickou analýzu tak, aby nová volná porovnání vyšla najevo při sestavení.
- Migrace na aktuální verze PHP doprovodíme testy pro změněná pravidla porovnávání.

Kontrolu vašeho softwaru popisuje [bezpečnostní audit a penetrační test](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Časté dotazy

**Co je type juggling v PHP?**  
PHP u některých operací automaticky převádí hodnoty různých typů, například řetězec na číslo. U volného porovnání pomocí == rozhoduje o výsledku právě tento převod. Je to pohodlné, ale může to vést k porovnáním, která jsou pravdivá, i když hodnoty nevypadají stejně.

**Vyřešilo PHP 8 tento problém?**  
Částečně. Od PHP 8.0 se číslo s nenumerickým řetězcem porovnává jako řetězec, takže 0 == 'a' už neplatí. Dva numerické řetězce se ale dál porovnávají jako čísla a porovnání s true, false a null se řídí vlastními pravidly.

**Pomáhá declare(strict_types=1) proti volným porovnáním?**  
Ne. strict_types řídí, zda se skalární typy při voláních funkcí a návratech převádějí automaticky. Na operátory porovnání == a != nemá vliv.

**Jak správně porovnávat hesla a tokeny?**  
Hesla ověřujte pomocí password_verify, tokeny a podpisy pomocí hash_equals. Obě porovnávají přesně a hash_equals v konstantním čase. Porovnání pomocí == nebo strcmp se k tomu nehodí.

## Zdroje

- [Příručka PHP: operátory porovnání](https://www.php.net/manual/de/language.operators.comparison.php)
- [Příručka PHP: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
