Dabartinė padėtis: PHP2Shell. Du kritiniai PHP pažeidžiamumai (CVSS 9,2) leidžia vykdyti kenkėjišką kodą be prisijungimo. Pataisos nuo 2026-10-21.Ką dabar daryti →

Type Juggling PHP: laisvi palyginimai kaip saugumo rizika

Kodėl laisvi palyginimai su == PHP gali sukelti saugumo spragų, ką pakeitė PHP 8.0 ir kaip patikimai rašyti palyginimus prisijungimuose, žetonuose ir teisėse.

Būsena 5 min. skaitymoSaugus programavimas

Trumpai: PHP Type Juggling reiškia automatinį tipų pavertimą, kuris laisvo palyginimo su == atveju lemia rezultatą. Nuo PHP 8.0 taisyklės griežtesnės, bet skaitinės simbolių eilutės ir toliau lyginamos kaip skaičiai, o true, false ir null seka savomis taisyklėmis. Saugumui svarbioms patikroms todėl galioja: ===, in_array(..., true), match vietoj switch, hash_equals() žetonams ir password_verify() slaptažodžiams.

Palyginimas, kuris teisingas, nors neturėtų būti

Daug PHP programų saugumo spragų slypi ne sudėtingose funkcijose, o viename simbolyje: ==, kur turėjo stovėti ===. Kodas atrodo teisingas, bandymai su įprasta įvestimi praeina, ir vis dėlto patikra tam tikromis sąlygomis praleidžia tai, ką turėjo sustabdyti.

Priežastis yra PHP savybė, kuri kasdienybėje praktiška: reikšmės prireikus paverčiamos kitu tipu. Simbolių eilutė iš formos taip be aplinkkelių gali būti lyginama su skaičiumi iš duomenų bazės. Prisijungimuose, žetonuose, rolėse ar būsenos laukuose būtent šis dosnumas gali tapti pavojingas.

Kaip PHP lygina su ==

PHP vadovas išsamiai aprašo laisvų palyginimų taisykles. Saugumui svarbiausi šie atvejai:

Palyginimas PHP 7 PHP 8 Priežastis
0 == "a" teisinga klaidinga nuo PHP 8.0 lyginama kaip simbolių eilutės
"1" == "01" teisinga teisinga abi skaitinės, lyginama kaip skaičiai
"10" == "1e1" teisinga teisinga eksponentinis užrašas yra skaitinis
100 == "1e2" teisinga teisinga skaičius prieš skaitinę simbolių eilutę
null == false teisinga teisinga lyginama kaip loginė reikšmė
"abc" == true teisinga teisinga netuščia simbolių eilutė laikoma teisinga

PHP 8.0 pakeitimas apėmė visą klaidų klasę: anksčiau neskaitinė simbolių eilutė lyginant su skaičiumi tapdavo 0. Tai veikė ir switch sakinius. Likusios lentelės eilutės rodo, kad laisvi palyginimai ir toliau slepia staigmenų.

Taip pat operatoriai <, > ir palyginimo operatorius <=> seka šias taisykles. Rikiavimuose tai dažniausiai nekenksminga, o tikrinant „suma mažesnė už limitą“ su reikšmėmis iš užklausos tipas turi būti žinomas iš anksto.

Ypač klastinga eilutė su eksponentiniu užrašu. Maišų (hash) reikšmės ir žetonai dažnai sudaryti iš skaitmenų ir raidžių. Jei tokia reikšmė atsitiktinai atrodo kaip skaičius eksponentiniu užrašu, == dvi skirtingas maišas (hash) gali palyginti kaip vienodas. Tai daugelio pranešimų apie prisijungimo apėjimus senesnėse PHP programose pagrindas.

PHP Type Juggling tipinėse saugumo patikrose

Laisvi palyginimai slepiasi daugiau vietų nei tik operatoriuje ==:

  1. in_array() ir array_search() be trečio parametro lygina laisvai. Rolių ar ID leidžiamųjų sąrašas dėl to tampa minkštesnis, nei manoma.
  2. switch lygina laisvai. Nuo PHP 8.0 prieinama išraiška match, pasak vadovo, priešingai lygina tipui saugiai su === ir meta klaidą, jei nė viena šaka netinka.
  3. JSON įvestys pateikia tikrus tipus. Laukas, kurio tikitės kaip simbolių eilutės, gali ateiti kaip true ar skaičius ir su == netikėtai sutapti.
  4. strcmp() paslaptims netinka, nes vykdymo laikas priklauso nuo turinio, o klaidų atvejai lengvai neteisingai įvertinami.
  5. Grąžinamų reikšmių patikra: funkcijos kaip strpos() grąžina 0 radiniui pradžioje ir false, jei radinio nėra. if (!strpos(...)) abu sumaišo.

Saugūs palyginimai: palyginimas greta

Toliau pateikti pavyzdžiai rodo nesaugų ir saugų variantą:

<?php
declare(strict_types=1);

// Patikrinti rolę
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // nesaugu: laisvai
in_array($rolle, $erlaubteRollen, true);    // saugu: griežtai

// Įvertinti būseną
$aktion = match ($status) {                 // griežtai, klaida esant nežinomai reikšmei
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Patikrinti žetoną iš nuorodos
if ($tokenAusLink == $gespeichertesToken) { /* nesaugu */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* saugu */ }

// Patikrinti slaptažodį
if (md5($eingabe) == $hash) { /* nesaugu: silpnas metodas ir laisvas palyginimas */ }
if (password_verify($eingabe, $hash)) { /* saugu */ }

// Ieškoti poeilutės
if (strpos($text, 'abc') !== false) { /* teisinga: ir radinys 0 pozicijoje */ }

hash_equals() pagal vadovą pirmu parametru tikisi žinomos paslapties, o antru naudotojo reikšmės, ir lygina pastoviu laiku. Kaip žetonus apskritai saugiai sukurti, aprašo Saugūs atsitiktiniai skaičiai ir žetonai PHP.

Svarbu

declare(strict_types=1) palyginimų griežtais nepadaro. Šis sakinys liečia tik tipų pavertimą kviečiant funkcijas ir grąžinant reikšmes. Laisvus palyginimus turite pakeisti patys kode.

Griežti palyginimai turi kitą pusę: jie nepavyksta, kai tipai nesutampa. '42' === 42 yra klaidinga. Reikšmės iš formų ir URL PHP ateina kaip simbolių eilutės, reikšmės iš duomenų bazės, priklausomai nuo tvarkyklės ir nustatymų, kaip simbolių eilutės ar skaičiai. Kas visur įveda === neišlygindamas tipų, sukuria klaidas, kurios veikiant pastebimos kaip teisėtų naudotojų atmetimas.

Sprendimas yra aiški riba: įvestys gaunant patikrinamos ir paverčiamos teisingu tipu, pavyzdžiui, su filter_var($wert, FILTER_VALIDATE_INT), o po to kode tvarkomos tik su tipų deklaracijomis ir griežtais palyginimais. Tipų deklaracijos parametrams, grąžinamoms reikšmėms ir savybėms padeda, nes neteisingi tipai tada anksti išryškėja su klaidos pranešimu, užuot tyliai pranykę palyginime.

Esamo kodo sisteminė patikra

Išaugusiuose projektuose laisvų palyginimų rasite šimtus. Ne kiekvienas yra problema. Susitelkite į vietas, kuriose sprendžiamas prieigos klausimas:

# Laisvi palyginimai eilutėse su saugumui svarbiais terminais
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array be griežto palyginimo
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Statinės analizės įrankiai kaip PHPStan ar Psalm gali pageidavimu nuolat pranešti apie laisvus palyginimus. Taip nauji atvejai net nepatenka į pagrindinę šaką. Tipų pagrindus aprašo PHP tipų sistema; kaip įvestys iš anksto paverčiamos teisingu tipu, rodo Įvesties patikra ir filtravimas PHP.

Ką tai reiškia senoms sistemoms

Programos, kurios dar veikia su PHP 7, paveiktos senų, laisvesnių palyginimo taisyklių. Be to, yra antra problema: PHP 8.1 ir senesnėms PHP projektas oficialių saugumo atnaujinimų nebeteikia, taip pat ir nuo PHP2Shell. Padėtį aprašo PHP 8.1 ir senesnės: kas dabar galioja.

Migruojant į PHP 8 kai kurie palyginimų rezultatai keičiasi. Tai dažniausiai pagerinimas, bet gali sulaužyti kodą, kuris rėmėsi senuoju elgesiu. Todėl suplanuokite bandymus, kurie tikslingai apima palyginimus su skaičiais, tuščiomis reikšmėmis ir null.

Kaip PHP SOS padeda konkrečiai?

  • Peržiūrime jūsų kodą ieškodami laisvų palyginimų vietose, kurios sprendžia dėl prisijungimo, teisių ar mokėjimų.
  • Pakeičiame juos griežtais palyginimais, match išraiškomis ir tinkamomis patikros funkcijomis.
  • Sukonfigūruojame statinę analizę taip, kad nauji laisvi palyginimai išryškėtų kuriant (build).
  • Migracijas į naujausias PHP versijas lydime bandymais pakeistoms palyginimo taisyklėms.

Jūsų programinės įrangos patikrą aprašo Saugumo auditas ir įsiskverbimo testas.

Dažni klausimai

Kas yra Type Juggling PHP?

PHP kai kurių operacijų metu skirtingų tipų reikšmes automatiškai paverčia, pavyzdžiui, simbolių eilutę skaičiumi. Laisvo palyginimo su == atveju rezultatą lemia šis pavertimas. Tai patogu, bet gali sukelti palyginimus, kurie teisingi, nors reikšmės neatrodo vienodos.

Ar PHP 8 išsprendė problemą?

Iš dalies. Nuo PHP 8.0 skaičius su neskaitine simbolių eilute lyginamas kaip simbolių eilutė, taigi 0 == 'a' nebėra teisinga. Tačiau dvi skaitinės simbolių eilutės ir toliau lyginamos kaip skaičiai, o palyginimai su true, false ir null seka savomis taisyklėmis.

Ar declare(strict_types=1) padeda nuo laisvų palyginimų?

Ne. strict_types valdo, ar skaliariniai tipai kviečiant funkcijas ir grąžinant reikšmes paverčiami automatiškai. Palyginimo operatoriams == ir != tai įtakos neturi.

Kaip teisingai lyginti slaptažodžius ir žetonus?

Slaptažodžius tikrinkite su password_verify, žetonus ir parašus su hash_equals. Abu lygina tiksliai, o hash_equals ir pastoviu laiku. Palyginimas su == ar strcmp tam netinka.

Šaltiniai

Skubus kontaktas

Kiekviena valanda svarbi. Parašykite mums dabar.

Trumpai aprašykite sistemą, PHP versiją ir pastebėjimus. Mes atsakysime, išsiaiškinsime padėtį ir dirbsime nuotoliu, kol incidentas bus užbaigtas ir dokumentuotas.

Skubi pagalba