Trenutno stanje: PHP2Shell. Dvije kritične PHP ranjivosti (CVSS 9.2) omogućuju zlonamjerni kod bez prijave. Zakrpe od 21. 10. 2026.Što sada učiniti →

Type Juggling u PHP-u: labave usporedbe kao sigurnosni rizik

Zašto labave usporedbe pomoću == u PHP-u mogu dovesti do sigurnosnih ranjivosti, što se promijenilo s PHP-om 8.0 i kako robusno pisati usporedbe u prijavama, tokenima i ovlastima.

Stanje 5 min čitanjaSiguran razvoj

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
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.

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:

# 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; kako se unosi prethodno dovode u ispravan tip, pokazuje Validirati i filtrirati unos u PHP-u.

Š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.

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.

Č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

Hitni kontakt

Svaki sat se računa. Pišite nam odmah.

Ukratko opišite sustav, verziju PHP-a i opažanje. Javit ćemo Vam se, razjasniti situaciju i raditi na daljinu dok incident nije zaključen i dokumentiran.

Hitna pomoć