Trenutno stanje: PHP2Shell. Dve kritični ranljivosti PHP (CVSS 9,2) omogočata izvedbo škodljive kode brez prijave. Popravki od 21. 10. 2026.Kaj je zdaj treba storiti →

Type juggling v PHP: ohlapne primerjave kot varnostno tveganje

Zakaj lahko ohlapne primerjave z == v PHP vodijo do varnostnih ranljivosti, kaj se je spremenilo s PHP 8.0 in kako robustno napišete primerjave pri prijavah, žetonih in pooblastilih.

Stanje 5 min branjaVaren razvoj

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

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:

# 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; kako vnose prej pretvorite v pravi tip, kaže Preverjanje in filtriranje vnosov v 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.

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.

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

Takojšnji stik

Vsaka ura šteje. Pišite nam zdaj.

Na kratko opišite sistem, različico PHP in opažanje. Odzovemo se, razjasnimo stanje in delamo na daljavo, dokler incident ni zaključen in dokumentiran.

Takojšnja pomoč