# Type Juggling u PHP-u: labave usporedbe kao sigurnosni rizik

> Razumjeti PHP Type Juggling: kako uspoređuje ==, što je promijenio PHP 8.0 i zašto se tokeni, hashovi i uloge moraju strogo uspoređivati.

Izvor: https://phpsos.de/hr/blog/type-juggling-php/  
Stanje: 25.10.2026  
Pružatelj: PHP SOS, PHP hitna pomoć i sigurnost poslužitelja. Hitna pomoć putem WhatsAppa: https://wa.me/491736494914?text=Pozdrav%20PHP%20SOS%2C%20trebamo%20pomo%C4%87%20s%20na%C5%A1im%20PHP%20poslu%C5%BEiteljem.%20Tvrtka%3A%20%E2%80%A6%20Web-stranica%3A%20%E2%80%A6

**Ukratko:** PHP Type Juggling označava automatsku pretvorbu tipova koja kod labave usporedbe pomoću `==` odlučuje o rezultatu. Od PHP-a 8.0 pravila su stroža, ali numerički nizovi znakova i dalje se uspoređuju kao brojevi, a `true`, `false` i `null` slijede vlastita pravila. Za sigurnosno relevantne provjere stoga vrijedi: `===`, `in_array(..., true)`, `match` umjesto `switch`, `hash_equals()` za tokene i `password_verify()` za lozinke.

## Usporedba koja je istinita iako ne bi smjela biti

Mnoge sigurnosne ranjivosti u PHP aplikacijama ne kriju se u složenim funkcijama, nego u jednom jedinom znaku: `==` tamo gdje je trebao stajati `===`. Kod izgleda ispravno, testovi s normalnim unosima prolaze, a ipak provjera pod određenim uvjetima propušta nešto što je trebala zaustaviti.

Uzrok je svojstvo PHP-a koje je u svakodnevici praktično: vrijednosti se po potrebi pretvaraju u drugi tip. Niz znakova iz obrasca tako se bez zaobilaznice može usporediti s brojem iz baze podataka. Kod prijava, tokena, uloga ili statusnih polja upravo ta velikodušnost može postati opasna.

## Kako PHP uspoređuje pomoću ==

PHP priručnik opširno opisuje pravila za labave usporedbe. Za sigurnost su ponajprije važni ovi slučajevi:

| Usporedba | PHP 7 | PHP 8 | Razlog |
|---|---|---|---|
| `0 == "a"` | istina | laž | od PHP-a 8.0 uspoređuju se kao nizovi znakova |
| `"1" == "01"` | istina | istina | oba numerička, usporedba kao broj |
| `"10" == "1e1"` | istina | istina | eksponencijalni zapis je numerički |
| `100 == "1e2"` | istina | istina | broj naspram numeričkog niza znakova |
| `null == false` | istina | istina | usporedba kao logička vrijednost |
| `"abc" == true` | istina | istina | neprazan niz znakova vrijedi kao istina |

Promjena u PHP-u 8.0 ublažila je cijelu klasu pogrešaka: ranije je nenumerički niz znakova pri usporedbi s brojem postajao `0`. To je pogađalo i `switch` naredbe. Ostali redci tablice ipak pokazuju da labave usporedbe i dalje kriju iznenađenja.

Operatori `<`, `>` i operator usporedbe `<=>` također slijede ta pravila. Kod sortiranja to je obično bezazleno, a kod provjera poput „iznos manji od limita“ s vrijednostima iz zahtjeva tip bi trebao biti utvrđen unaprijed.

Osobito je podmukao redak s eksponencijalnim zapisom. Hash vrijednosti i tokeni često se sastoje od znamenki i slova. Ako takva vrijednost slučajno izgleda kao broj u eksponencijalnom zapisu, `==` dva različita hasha možda uspoređuje kao jednaka. To je pozadina mnogih izvješća o zaobilaženju prijave u starijim PHP aplikacijama.

## PHP Type Juggling u tipičnim sigurnosnim provjerama

Labave usporedbe skrivaju se na više mjesta nego samo u operatoru `==`:

1. **`in_array()` i `array_search()`** bez trećeg parametra uspoređuju labavo. Popis dopuštenog s ulogama ili ID-ovima time postaje mekši nego što se misli.
2. **`switch`** uspoređuje labavo. Izraz `match`, dostupan od PHP-a 8.0, prema priručniku pak uspoređuje tipski sigurno pomoću `===` i baca pogrešku ako nijedna grana ne odgovara.
3. **JSON unosi** donose prave tipove. Polje koje očekujete kao niz znakova može stići kao `true` ili kao broj i pomoću `==` neočekivano se podudarati.
4. **`strcmp()` za tajne** nije prikladan jer trajanje ovisi o sadržaju, a slučajevi pogreške lako se pogrešno ocijene.
5. **Provjera povratnih vrijednosti**: funkcije poput `strpos()` vraćaju `0` za pogodak na početku i `false` za nepostojanje pogotka. `if (!strpos(...))` zamjenjuje jedno s drugim.

## Sigurne usporedbe: usporedni prikaz

Sljedeći primjeri svaki put pokazuju nesigurnu i sigurnu varijantu:

```php
<?php
declare(strict_types=1);

// Provjeriti ulogu
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nesigurno: labavo
in_array($rolle, $erlaubteRollen, true);    // sigurno: strogo

// Ocijeniti status
$aktion = match ($status) {                 // strogo, pogreška kod nepoznate vrijednosti
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Provjeriti token iz poveznice
if ($tokenAusLink == $gespeichertesToken) { /* nesigurno */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* sigurno */ }

// Provjeriti lozinku
if (md5($eingabe) == $hash) { /* nesigurno: slab postupak i labava usporedba */ }
if (password_verify($eingabe, $hash)) { /* sigurno */ }

// Tražiti podniz
if (strpos($text, 'abc') !== false) { /* ispravno: i pogodak na poziciji 0 */ }
```

`hash_equals()` prema priručniku kao prvi parametar očekuje poznatu tajnu, a kao drugi vrijednost korisnika, i uspoređuje u konstantnom vremenu. Kako tokene uopće sigurno stvarati, opisuje [Sigurni slučajni brojevi i tokeni u PHP-u](https://phpsos.de/hr/blog/sichere-tokens-php/).

> **Važno:**
> `declare(strict_types=1)` ne čini usporedbe strogima. Naredba se tiče samo pretvorbe tipova pri pozivima funkcija i povratnim vrijednostima. Labave usporedbe morate sami zamijeniti u kodu.

Stroge usporedbe imaju i svoju lošu stranu: propadaju kad se tipovi ne podudaraju. `'42' === 42` je laž. Vrijednosti iz obrazaca i URL-ova u PHP-u stižu kao nizovi znakova, a vrijednosti iz baze podataka ovisno o driveru i postavci kao nizovi znakova ili brojevi. Tko posvuda uvede `===` a da ne uskladi tipove, stvara pogreške koje se u radu pokazuju kao odbijanje opravdanih korisnika.

Rješenje je jasna granica: unosi se pri primitku validiraju i dovode u ispravan tip, primjerice pomoću `filter_var($wert, FILTER_VALIDATE_INT)`, a zatim se u kodu tretiraju samo deklaracijama tipova i strogim usporedbama. Deklaracije tipova na parametrima, povratnim vrijednostima i svojstvima tome pomažu jer se pogrešni tipovi tada rano uoče porukom o pogrešci, umjesto da tiho nestanu u usporedbi.

## Sustavno provjeriti postojeći kod

U razvijenim projektima labavih usporedbi ima na stotine. Nije svaka problem. Usredotočite se na mjesta na kojima se odlučuje o pristupu:

```bash
# Labave usporedbe u retcima sa sigurnosno relevantnim pojmovima
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array bez stroge usporedbe
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
```

Alati za statičku analizu poput PHPStana ili Psalma na zahtjev mogu trajno prijavljivati labave usporedbe. Tako novi slučajevi uopće ne dospijevaju u glavnu granu. Osnove o tipovima opisuje [Sustav tipova PHP-a](https://phpsos.de/hr/php/typsystem/); kako se unosi prethodno dovode u ispravan tip, pokazuje [Validirati i filtrirati unos u PHP-u](https://phpsos.de/hr/blog/eingaben-validieren-php/).

## Što to znači za stare sustave

Aplikacije koje još rade na PHP-u 7 pogođene su starim, labavijim pravilima usporedbe. Tome se pridružuje drugi problem: za PHP 8.1 i starije PHP projekt više ne daje službene sigurnosne updatee, ni protiv PHP2Shella. Stanje opisuje [PHP 8.1 i starije: što sada vrijedi](https://phpsos.de/hr/php2shell/php-8-1-und-aelter/).

Pri migraciji na PHP 8 neki rezultati usporedbi mijenjaju se. To je obično poboljšanje, ali može slomiti kod koji se oslanjao na staro ponašanje. Zato planirajte testove koji ciljano pokrivaju usporedbe s brojevima, praznim vrijednostima i `null`.

## Kako PHP SOS konkretno pomaže?

- Pretražujemo Vaš kod u potrazi za labavim usporedbama na mjestima koja odlučuju o prijavi, pravima ili plaćanjima.
- Zamjenjujemo ih strogim usporedbama, `match` izrazima i odgovarajućim funkcijama za provjeru.
- Postavljamo statičku analizu tako da se nove labave usporedbe uoče u buildu.
- Pratimo migracije na aktualne PHP verzije testovima za promijenjena pravila usporedbe.

Provjeru Vašeg softvera opisuje [Sigurnosni audit i penetracijski test](https://phpsos.de/hr/leistungen/sicherheitsaudit-penetrationstest/).


## Česta pitanja

**Što je Type Juggling u PHP-u?**  
PHP kod nekih operacija vrijednosti različitih tipova automatski pretvara, primjerice niz znakova u broj. Kod labave usporedbe pomoću == ta pretvorba odlučuje o rezultatu. To je zgodno, ali može dovesti do usporedbi koje su istinite iako vrijednosti ne izgledaju jednako.

**Je li PHP 8 riješio problem?**  
Djelomično. Od PHP-a 8.0 broj se s nenumeričkim nizom znakova uspoređuje kao niz znakova, dakle 0 == 'a' više nije istina. Dva numerička niza znakova ipak se i dalje uspoređuju kao brojevi, a usporedbe s true, false i null slijede vlastita pravila.

**Pomaže li declare(strict_types=1) protiv labavih usporedbi?**  
Ne. strict_types određuje pretvaraju li se skalarni tipovi automatski pri pozivima funkcija i povratnim vrijednostima. Na operatore usporedbe == i != nema utjecaja.

**Kako ispravno usporediti lozinke i tokene?**  
Lozinke provjeravate pomoću password_verify, tokene i potpise pomoću hash_equals. Obje uspoređuju točno i, kod hash_equals, u konstantnom vremenu. Usporedba pomoću == ili strcmp za to je neprikladna.

## Izvori

- [PHP priručnik: operatori usporedbe](https://www.php.net/manual/de/language.operators.comparison.php)
- [PHP priručnik: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
