# Type Juggling PHP-s: lõdvad võrdlused turvariskina

> PHP Type Juggling selgeks: kuidas == võrdleb, mida PHP 8.0 muutis ning miks tokeneid, räsisid ja rolle tuleb võrrelda rangelt. Koos koodinäidetega.

Allikas: https://phpsos.de/et/blog/type-juggling-php/  
Seis: 25.10.2026  
Pakkuja: PHP SOS, PHP hädaabi ja serveri turvalisus. Kohene abi WhatsAppis: https://wa.me/491736494914?text=Tere%2C%20PHP%20SOS%2C%20vajame%20abi%20oma%20PHP-serveriga.%20Ettev%C3%B5te%3A%20%E2%80%A6%20Veebisait%3A%20%E2%80%A6

**Lühidalt:** PHP Type Juggling tähendab automaatset tüübiteisendust, mis otsustab lõdva võrdluse puhul märgiga `==` tulemuse. PHP 8.0-st on reeglid rangemad, kuid numbrilisi sõnesid võrreldakse endiselt arvudena ning `true`, `false` ja `null` järgivad oma reegleid. Turvaküsimuste kontrollide jaoks kehtib seetõttu: `===`, `in_array(..., true)`, `match` `switch`-i asemel, `hash_equals()` tokenite jaoks ja `password_verify()` paroolide jaoks.

## Võrdlus, mis on tõene, kuigi ei peaks olema

Paljud PHP-rakenduste turvaaugud ei peitu keerukates funktsioonides, vaid ühes ainsas märgis: `==`, kus oleks pidanud olema `===`. Kood näeb õige välja, testid tavaliste sisenditega lähevad läbi ja ometi laseb kontroll teatud tingimustel läbi midagi, mida see peaks peatama.

Põhjus on PHP omadus, mis igapäevaselt on praktiline: väärtused teisendatakse vajaduse korral teise tüüpi. Vormist pärit sõne saab nii ilma ringkäiguta võrrelda andmebaasist pärit arvuga. Sisselogimiste, tokenite, rollide või olekuväljade puhul võib just see helde suhtumine ohtlikuks muutuda.

## Kuidas PHP märgiga == võrdleb

PHP käsiraamat kirjeldab lõdvade võrdluste reegleid põhjalikult. Turvalisuse jaoks on olulised eelkõige need juhud:

| Võrdlus | PHP 7 | PHP 8 | Põhjus |
|---|---|---|---|
| `0 == "a"` | tõene | väär | PHP 8.0-st võrreldakse sõnedena |
| `"1" == "01"` | tõene | tõene | mõlemad numbrilised, võrdlus arvuna |
| `"10" == "1e1"` | tõene | tõene | eksponentkuju on numbriline |
| `100 == "1e2"` | tõene | tõene | arv numbrilise sõne vastu |
| `null == false` | tõene | tõene | võrdlus tõeväärtusena |
| `"abc" == true` | tõene | tõene | mittetühja sõnet peetakse tõeseks |

PHP 8.0 muudatus leevendas tervet klassi vigu: varem muutus mittenumbriline sõne arvuga võrdlemisel väärtuseks `0`. See puudutas ka `switch`-lauseid. Tabeli ülejäänud read näitavad aga, et lõdvad võrdlused sisaldavad endiselt üllatusi.

Ka operaatorid `<`, `>` ja võrdlusoperaator `<=>` järgivad neid reegleid. Sorteerimise puhul on see enamasti kahjutu, kuid kontrollide puhul nagu „summa väiksem kui limiit“ päringust pärit väärtustega peaks tüüp olema eelnevalt kindel.

Eriti salakaval on eksponentkujuga rida. Räsiväärtused ja tokenid koosnevad sageli numbritest ja tähtedest. Kui selline väärtus näeb juhuslikult välja nagu arv eksponentkujul, võrdleb `==` kaks erinevat räsi võib-olla võrdseks. See on taust paljudele aruannetele sisselogimise möödapääsemise kohta vanemates PHP-rakendustes.

## PHP Type Juggling tüüpilistes turvakontrollides

Lõdvad võrdlused peituvad rohkemates kohtades kui ainult operaatoris `==`:

1. **`in_array()` ja `array_search()`** võrdlevad ilma kolmanda parameetrita lõdvalt. Rollide või ID-de lubatud väärtuste nimekiri muutub seeläbi pehmemaks, kui arvate.
2. **`switch`** võrdleb lõdvalt. PHP 8.0-st saadaval olev avaldis `match` võrdleb käsiraamatu kohaselt seevastu tüübikindlalt märgiga `===` ja viskab vea, kui ükski haru ei sobi.
3. **JSON-sisendid** annavad tegelikud tüübid. Väli, mida ootate sõnena, võib saabuda väärtusena `true` või arvuna ja märgiga `==` üllatavalt kokku langeda.
4. **`strcmp()` saladuste jaoks** ei sobi, sest tööaeg sõltub sisust ja veajuhud hinnatakse kergesti valesti.
5. **Tagastusväärtuste kontroll**: funktsioonid nagu `strpos()` tagastavad `0` leiu korral alguses ja `false` leiu puudumisel. `if (!strpos(...))` ajab mõlemad segi.

## Turvalised võrdlused: vastandamine

Järgmised näited näitavad igal juhul ebaturvalist ja turvalist varianti:

```php
<?php
declare(strict_types=1);

// Rolli kontroll
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // ebaturvaline: lõdv
in_array($rolle, $erlaubteRollen, true);    // turvaline: range

// Oleku hindamine
$aktion = match ($status) {                 // range, tundmatu väärtuse korral viga
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Lingist pärit tokeni kontroll
if ($tokenAusLink == $gespeichertesToken) { /* ebaturvaline */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* turvaline */ }

// Parooli kontroll
if (md5($eingabe) == $hash) { /* ebaturvaline: nõrk meetod ja lõdv võrdlus */ }
if (password_verify($eingabe, $hash)) { /* turvaline */ }

// Alamsõne otsimine
if (strpos($text, 'abc') !== false) { /* õige: ka leid positsioonil 0 */ }
```

`hash_equals()` ootab käsiraamatu kohaselt esimese parameetrina teadaolevat saladust ja teisena kasutaja väärtust ning võrdleb konstantse ajaga. Kuidas tokeneid üldse turvaliselt luua, kirjeldab [Turvalised juhuarvud ja tokenid PHP-s](https://phpsos.de/et/blog/sichere-tokens-php/).

> **Oluline:**
> `declare(strict_types=1)` ei muuda võrdlusi rangeks. Lause puudutab ainult tüübiteisendust funktsioonikutsetes ja tagastusväärtustes. Lõdvad võrdlused peate koodis ise asendama.

Rangetel võrdlustel on teine külg: need ebaõnnestuvad, kui tüübid ei sobi kokku. `'42' === 42` on väär. Vormidest ja URL-idest pärit väärtused saabuvad PHP-s sõnedena, andmebaasist pärit väärtused sõltuvalt draiverist ja sättest sõnede või arvudena. Kes viib igale poole sisse `===`, ilma tüüpe ühtlustamata, tekitab vigu, mis tööl märgatakse õigustatud kasutajate tagasilükkamisena.

Lahendus on selge piir: sisendid valideeritakse vastuvõtul ja viiakse õigesse tüüpi, näiteks `filter_var($wert, FILTER_VALIDATE_INT)` abil, ning seejärel käsitletakse koodis ainult tüübideklaratsioonide ja rangete võrdlustega. Tüübideklaratsioonid parameetritel, tagastustel ja omadustel aitavad selles, sest valed tüübid tulevad siis varakult veateatega ilmsiks, selle asemel et vaikselt võrdluses kaduda.

## Olemasoleva koodi süstemaatiline kontroll

Kasvanud projektides on lõdvaid võrdlusi sadu. Mitte iga neist ei ole probleem. Keskenduge kohtadele, kus langetatakse otsus juurdepääsu üle:

```bash
# Lõdvad võrdlused ridades, kus on turvaküsimustega seotud mõisted
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array ilma range võrdluseta
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
```

Staatilise analüüsi tööriistad, nagu PHPStan või Psalm, saavad lõdvaid võrdlusi soovi korral püsivalt teatada. Nii ei jõua uued juhud põhiharusse üldse. Tüüpide alused kirjeldab [PHP tüübisüsteem](https://phpsos.de/et/php/typsystem/); kuidas sisendid enne õigesse tüüpi viia, näitab [Sisendi valideerimine ja filtreerimine PHP-s](https://phpsos.de/et/blog/eingaben-validieren-php/).

## Mida see tähendab vanade süsteemide jaoks

Rakendused, mis töötavad veel PHP 7-l, on mõjutatud vanadest, lõdvematest võrdlusreeglitest. Lisandub teine probleem: PHP 8.1 ja vanemate jaoks ei ole PHP projektil enam ametlikke turvauuendusi, ka mitte PHP2Shelli vastu. Olukorra kirjeldab [PHP 8.1 ja vanemad: mis nüüd kehtib](https://phpsos.de/et/php2shell/php-8-1-und-aelter/).

PHP 8-le migreerimisel muutuvad mõned võrdlustulemused. See on tavaliselt parandus, kuid võib rikkuda koodi, mis tugines vanale käitumisele. Planeerige seetõttu teste, mis katavad sihipäraselt võrdlusi arvude, tühjade väärtuste ja `null`-iga.

## Kuidas PHP SOS konkreetselt aitab?

- Me otsime teie koodist lõdvaid võrdlusi kohtades, mis otsustavad sisselogimise, õiguste või maksete üle.
- Me asendame need rangete võrdluste, `match`-avaldiste ja sobivate kontrollfunktsioonidega.
- Me seadistame staatilise analüüsi nii, et uued lõdvad võrdlused ilmnevad buildis.
- Me toetame migratsioone praegustele PHP versioonidele testidega muutunud võrdlusreeglite jaoks.

Teie tarkvara kontrolli kirjeldab [Turvaaudit ja läbitungimistest](https://phpsos.de/et/leistungen/sicherheitsaudit-penetrationstest/).


## Korduma kippuvad küsimused

**Mis on Type Juggling PHP-s?**  
PHP teisendab mõne toimingu puhul eri tüüpi väärtused automaatselt, näiteks sõne arvuks. Lõdva võrdluse puhul märgiga == otsustab see teisendus tulemuse. See on mugav, kuid võib viia võrdlusteni, mis on tõesed, kuigi väärtused ei näe võrdsed välja.

**Kas PHP 8 lahendas probleemi?**  
Osaliselt. PHP 8.0-st võrreldakse arvu mittenumbrilise sõnega sõnena, seega 0 == 'a' ei ole enam tõene. Kaks numbrilist sõne võrreldakse aga endiselt arvudena ning võrdlused väärtustega true, false ja null järgivad oma reegleid.

**Kas declare(strict_types=1) aitab lõdvade võrdluste vastu?**  
Ei. strict_types juhib, kas skalaartüübid funktsioonikutsetes ja tagastustes automaatselt teisendatakse. Võrdlusoperaatoritele == ja != see mõju ei avalda.

**Kuidas ma paroole ja tokeneid õigesti võrdlen?**  
Paroole kontrollite funktsiooniga password_verify, tokeneid ja allkirju funktsiooniga hash_equals. Mõlemad võrdlevad täpselt ja hash_equals konstantse ajaga. Võrdlus märgiga == või strcmp ei sobi selleks.

## Allikad

- [PHP käsiraamat: võrdlusoperaatorid](https://www.php.net/manual/de/language.operators.comparison.php)
- [PHP käsiraamat: match](https://www.php.net/manual/de/control-structures.match.php)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
