# Type juggling v PHP: ohlapne primerjave kot varnostno tveganje

> Razumevanje type jugglinga v PHP: kako primerja ==, kaj je spremenil PHP 8.0 in zakaj je treba žetone, zgoščene vrednosti in vloge primerjati strogo.

Vir: https://phpsos.de/sl/blog/type-juggling-php/  
Stanje: 25.10.2026  
Ponudnik: PHP SOS, Nujna pomoč za PHP in varnost strežnikov. Takojšnja pomoč po WhatsAppu: https://wa.me/491736494914?text=Pozdravljeni%2C%20PHP%20SOS%2C%20potrebujemo%20pomo%C4%8D%20z%20na%C5%A1im%20stre%C5%BEnikom%20PHP.%20Podjetje%3A%20%E2%80%A6%20Spletna%20stran%3A%20%E2%80%A6

**Na kratko:** Type juggling v PHP je samodejna pretvorba tipov, ki pri ohlapni primerjavi z `==` odloča o rezultatu. Od PHP 8.0 so pravila strožja, a numerični nizi se še vedno primerjajo kot števila, `true`, `false` in `null` pa sledijo lastnim pravilom. Za varnostno pomembna preverjanja zato velja: `===`, `in_array(..., true)`, `match` namesto `switch`, `hash_equals()` za žetone in `password_verify()` za gesla.

## Primerjava, ki je resnična, čeprav ne bi smela biti

Mnoge varnostne ranljivosti v aplikacijah PHP ne tičijo v zapletenih funkcijah, ampak v enem samem znaku: v `==`, kjer bi moral stati `===`. Koda izgleda pravilna, preizkusi z običajnimi vnosi uspejo, a preverjanje pod določenimi pogoji kljub temu spusti skozi nekaj, kar bi moralo ustaviti.

Vzrok je lastnost PHP, ki je v vsakdanu praktična: vrednosti se po potrebi pretvorijo v drug tip. Niz iz obrazca je tako mogoče brez ovinkov primerjati s številom iz baze podatkov. Pri prijavah, žetonih, vlogah ali poljih stanja ravno ta radodarnost lahko postane nevarna.

## Kako PHP primerja z ==

Priročnik PHP pravila za ohlapne primerjave podrobno opisuje. Za varnost so predvsem pomembni ti primeri:

| Primerjava | PHP 7 | PHP 8 | Razlog |
|---|---|---|---|
| `0 == "a"` | resnično | neresnično | od PHP 8.0 se primerjata kot niza |
| `"1" == "01"` | resnično | resnično | oba numerična, primerjava kot število |
| `"10" == "1e1"` | resnično | resnično | eksponentni zapis je numeričen |
| `100 == "1e2"` | resnično | resnično | število proti numeričnemu nizu |
| `null == false` | resnično | resnično | primerjava kot logična vrednost |
| `"abc" == true` | resnično | resnično | neprazen niz velja za resničnega |

Sprememba v PHP 8.0 je omilila cel razred napak: prej se je nenumerični niz pri primerjavi s številom pretvoril v `0`. To je zadevalo tudi stavke `switch`. Preostale vrstice tabele pa kažejo, da ohlapne primerjave še vedno skrivajo presenečenja.

Tudi operatorja `<`, `>` in primerjalni operator `<=>` sledijo tem pravilom. Pri razvrščanju je to večinoma neškodljivo, pri preverjanjih, kot je „znesek manjši od limita“, z vrednostmi iz zahteve pa naj bo tip določen že prej.

Posebej zahrbtna je vrstica z eksponentnim zapisom. Zgoščene vrednosti in žetoni pogosto sestojijo iz številk in črk. Če taka vrednost po naključju izgleda kot število v eksponentnem zapisu, `==` dve različni zgoščeni vrednosti morda primerja kot enaki. To je ozadje številnih poročil o obhodih prijave v starejših aplikacijah PHP.

## Type juggling v PHP pri tipičnih varnostnih preverjanjih

Ohlapne primerjave se skrivajo na več mestih kot samo v operatorju `==`:

1. **`in_array()` in `array_search()`** brez tretjega parametra primerjata ohlapno. Dovoljeni seznam z vlogami ali ID-ji je s tem mehkejši, kot bi mislili.
2. **`switch`** primerja ohlapno. Izraz `match`, ki je na voljo od PHP 8.0, pa po priročniku primerja tipsko varno z `===` in sproži napako, če nobena veja ne ustreza.
3. **Vnosi JSON** dajo prave tipe. Polje, ki ga pričakujete kot niz, lahko prispe kot `true` ali kot število in se z `==` presenetljivo ujema.
4. **`strcmp()` za skrivnosti** je neprimeren, ker je čas izvajanja odvisen od vsebine in se primeri napak zlahka napačno ovrednotijo.
5. **Preverjanje vrnjenih vrednosti**: funkcije, kot je `strpos()`, vrnejo `0` za zadetek na začetku in `false` za brez zadetka. `if (!strpos(...))` oboje zamenja.

## Varne primerjave: primerjava ob strani

Naslednji primeri vsakič kažejo nevarno in varno različico:

```php
<?php
declare(strict_types=1);

// Preverjanje vloge
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nevarno: ohlapno
in_array($rolle, $erlaubteRollen, true);    // varno: strogo

// Ovrednotenje stanja
$aktion = match ($status) {                 // strogo, napaka pri neznani vrednosti
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Preverjanje žetona iz povezave
if ($tokenAusLink == $gespeichertesToken) { /* nevarno */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* varno */ }

// Preverjanje gesla
if (md5($eingabe) == $hash) { /* nevarno: šibek algoritem in ohlapna primerjava */ }
if (password_verify($eingabe, $hash)) { /* varno */ }

// Iskanje podniza
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }
```

`hash_equals()` po priročniku kot prvi parameter pričakuje znano skrivnost, kot drugega pa vrednost uporabnika, in primerja v konstantnem času. Kako žetone sploh varno ustvarite, opisuje [Varna naključna števila in žetoni v PHP](https://phpsos.de/sl/blog/sichere-tokens-php/).

> **Pomembno:**
> `declare(strict_types=1)` primerjav ne naredi strogih. Ukaz zadeva samo pretvorbo tipov pri klicih funkcij in vrnjenih vrednostih. Ohlapne primerjave morate v kodi nadomestiti sami.

Stroge primerjave imajo tudi slabo stran: spodletijo, če se tipi ne ujemajo. `'42' === 42` je neresnično. Vrednosti iz obrazcev in URL-jev v PHP prispejo kot nizi, vrednosti iz baze podatkov glede na gonilnik in nastavitev kot nizi ali števila. Kdor povsod uvede `===`, ne da bi tipe uskladil, ustvari napake, ki se v delovanju kažejo kot zavrnitev upravičenih uporabnikov.

Rešitev je jasna meja: vnosi se ob prejemu preverijo in pretvorijo v pravi tip, na primer s `filter_var($wert, FILTER_VALIDATE_INT)`, nato pa se v kodi obravnavajo samo še z deklaracijami tipov in strogimi primerjavami. Deklaracije tipov pri parametrih, vrnjenih vrednostih in lastnostih pri tem pomagajo, ker se napačni tipi tako zgodaj pokažejo z napako, namesto da bi tiho izginili v primerjavi.

## Sistematičen pregled obstoječe kode

V zraslih projektih se ohlapne primerjave najdejo v stotinah. Vse niso težava. Osredotočite se na mesta, kjer pade odločitev o dostopu:

```bash
# Ohlapne primerjave v vrsticah z varnostno pomembnimi izrazi
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array brez stroge primerjave
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
```

Orodja za statično analizo, kot sta PHPStan ali Psalm, lahko ohlapne primerjave na željo trajno javljajo. Tako novi primeri sploh ne pridejo v glavno vejo. Osnove o tipih opisuje [Sistem tipov v PHP](https://phpsos.de/sl/php/typsystem/); kako vnose prej pretvorite v pravi tip, kaže [Preverjanje in filtriranje vnosov v PHP](https://phpsos.de/sl/blog/eingaben-validieren-php/).

## Kaj to pomeni za stare sisteme

Aplikacije, ki še tečejo na PHP 7, so prizadete zaradi starih, ohlapnejših pravil primerjav. K temu pride druga težava: za PHP 8.1 in starejše projekt PHP ne izdaja več uradnih varnostnih posodobitev, tudi proti PHP2Shell ne. Stanje opisuje [PHP 8.1 in starejši: kaj velja zdaj](https://phpsos.de/sl/php2shell/php-8-1-und-aelter/).

Pri migraciji na PHP 8 se nekateri rezultati primerjav spremenijo. To je večinoma izboljšanje, lahko pa pokvari kodo, ki se je zanašala na staro vedenje. Zato načrtujte preizkuse, ki ciljno pokrivajo primerjave s števili, praznimi vrednostmi in `null`.

## Kako konkretno pomaga PHP SOS?

- V vaši kodi poiščemo ohlapne primerjave na mestih, ki odločajo o prijavi, pravicah ali plačilih.
- Nadomestimo jih s strogimi primerjavami, izrazi `match` in ustreznimi funkcijami za preverjanje.
- Statično analizo nastavimo tako, da nove ohlapne primerjave opazimo že v gradnji.
- Migracije na aktualne različice PHP spremljamo s preizkusi za spremenjena pravila primerjav.

Pregled vaše programske opreme opisuje [Varnostni pregled in penetracijski test](https://phpsos.de/sl/leistungen/sicherheitsaudit-penetrationstest/).


## Pogosta vprašanja

**Kaj je type juggling v PHP?**  
PHP pri nekaterih operacijah vrednosti različnih tipov samodejno pretvori, na primer niz v število. Pri ohlapni primerjavi z == o rezultatu odloča ta pretvorba. To je udobno, a lahko vodi do primerjav, ki so resnične, čeprav vrednosti ne izgledajo enako.

**Ali je PHP 8 težavo rešil?**  
Delno. Od PHP 8.0 se število z nenumeričnim nizom primerja kot niz, torej 0 == 'a' ni več resnično. Dva numerična niza pa se še vedno primerjata kot števili, primerjave s true, false in null pa sledijo lastnim pravilom.

**Ali declare(strict_types=1) pomaga proti ohlapnim primerjavam?**  
Ne. strict_types nadzoruje, ali se skalarni tipi pri klicih funkcij in vrnjenih vrednostih samodejno pretvarjajo. Na primerjalna operatorja == in != nima vpliva.

**Kako pravilno primerjam gesla in žetone?**  
Gesla preverjate s password_verify, žetone in podpise s hash_equals. Obe primerjata natančno in, pri hash_equals, v konstantnem času. Primerjava z == ali strcmp je za to neprimerna.

## Viri

- [Priročnik PHP: primerjalni operatorji](https://www.php.net/manual/de/language.operators.comparison.php)
- [Priročnik PHP: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
