Praegune olukord: PHP2Shell. Kaks kriitilist PHP turvaauku (CVSS 9,2) lubavad kahjulikku koodi ilma sisselogimata. Paigad alates 21.10.2026.Mida nüüd teha →

Type Juggling PHP-s: lõdvad võrdlused turvariskina

Miks võivad lõdvad võrdlused märgiga == PHP-s viia turvaaukudeni, mida PHP 8.0 muutis ja kuidas kirjutada võrdlused sisselogimistes, tokenites ja õigustes töökindlaks.

Seis Lugemisaeg 5 minTurvaline arendus

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

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:

# 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; kuidas sisendid enne õigesse tüüpi viia, näitab Sisendi valideerimine ja filtreerimine PHP-s.

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.

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.

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

Kohene kontakt

Iga tund loeb. Kirjutage meile kohe.

Kirjeldage lühidalt süsteemi, PHP versiooni ja tähelepanekut. Võtame teiega ühendust, selgitame olukorra välja ja töötame kaugtööna, kuni intsident on lõpetatud ja dokumenteeritud.

Kohene abi