Īsi sakot: PHP Type Juggling apzīmē automātisko tipu pārveidi, kas brīvā salīdzinājumā ar == nosaka rezultātu. Kopš PHP 8.0 noteikumi ir stingrāki, taču numeriskas virknes joprojām salīdzina kā skaitļus, un true, false un null ir savi noteikumi. Drošībai svarīgām pārbaudēm tāpēc ir spēkā: ===, in_array(..., true), match switch vietā, hash_equals() tokeniem un password_verify() parolēm.
Salīdzinājums, kas ir patiess, lai gan nedrīkstētu būt
Daudzas drošības nepilnības PHP lietojumprogrammās slēpjas nevis sarežģītās funkcijās, bet vienā rakstzīmē: == vietā, kur vajadzēja ===. Kods izskatās pareizs, testi ar parastu ievadi iziet, un tomēr noteiktos apstākļos pārbaude ļauj notikt tam, ko tai vajadzēja apturēt.
Cēlonis ir PHP īpašība, kas ikdienā ir praktiska: vērtības pēc vajadzības tiek pārveidotas citā tipā. Virkni no veidlapas tā var bez līkločiem salīdzināt ar skaitli no datubāzes. Pieteikšanās, tokenu, lomu vai statusa lauku gadījumā tieši šī devība var kļūt bīstama.
Kā PHP salīdzina ar ==
PHP rokasgrāmata brīvo salīdzinājumu noteikumus apraksta detalizēti. Drošībai svarīgi ir galvenokārt šie gadījumi:
| Salīdzinājums | PHP 7 | PHP 8 | Iemesls |
|---|---|---|---|
0 == "a" |
patiess | nepatiess | kopš PHP 8.0 salīdzina kā virknes |
"1" == "01" |
patiess | patiess | abas numeriskas, salīdzinājums kā skaitļi |
"10" == "1e1" |
patiess | patiess | eksponenciālais pieraksts ir numerisks |
100 == "1e2" |
patiess | patiess | skaitlis pret numerisku virkni |
null == false |
patiess | patiess | salīdzinājums kā patiesuma vērtība |
"abc" == true |
patiess | patiess | netukša virkne skaitās patiesa |
Izmaiņa PHP 8.0 ir mīkstinājusi veselu kļūdu klasi: agrāk nenumeriska virkne salīdzinājumā ar skaitli kļuva par 0. Tas skāra arī switch izteikumus. Tabulas pārējās rindas tomēr rāda, ka brīvie salīdzinājumi joprojām slēpj pārsteigumus.
Šiem noteikumiem seko arī operatori <, > un salīdzināšanas operators <=>. Šķirošanā tas parasti ir nekaitīgi, bet pārbaudēs, piemēram, „summa mazāka par limitu" ar vērtībām no pieprasījuma tips jānosaka iepriekš.
Īpaši mānīga ir rinda ar eksponenciālo pierakstu. Jaucējkodu vērtības un tokeni bieži sastāv no cipariem un burtiem. Ja šāda vērtība nejauši izskatās kā skaitlis eksponenciālā pierakstā, == divus dažādus jaucējkodus var uzskatīt par vienādiem. Tas ir fons daudziem ziņojumiem par pieteikšanās apiešanu vecākās PHP lietojumprogrammās.
PHP Type Juggling tipiskās drošības pārbaudēs
Brīvie salīdzinājumi slēpjas vairākās vietās, ne tikai operatorā ==:
in_array()unarray_search()bez trešā parametra salīdzina brīvi. Lomu vai ID atļauju saraksts tādējādi kļūst mīkstāks, nekā domāts.switchsalīdzina brīvi. Kopš PHP 8.0 pieejamā izteiksmematchsaskaņā ar rokasgrāmatu turpretī salīdzina tipdrošī ar===un met kļūdu, ja neder neviens zars.- JSON ievade dod īstus tipus. Lauks, ko gaidāt kā virkni, var ierasties kā
truevai kā skaitlis un ar==negaidīti sakrist. strcmp()noslēpumiem nav piemērots, jo izpildes laiks atkarīgs no satura un kļūdu gadījumi viegli tiek nepareizi izvērtēti.- Atgriežamo vērtību pārbaude: tādas funkcijas kā
strpos()atdod0par trāpījumu sākumā unfalsepar trāpījuma neesamību.if (!strpos(...))abus sajauc.
Droši salīdzinājumi: pretstatījums
Turpmākie piemēri katrā gadījumā rāda nedrošo un drošo variantu:
<?php
declare(strict_types=1);
// Pārbaudīt lomu
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen); // nedroši: brīvi
in_array($rolle, $erlaubteRollen, true); // droši: stingri
// Izvērtēt statusu
$aktion = match ($status) { // stingri, kļūda nezināmai vērtībai
'offen' => 'bearbeiten',
'erledigt' => 'archivieren',
};
// Pārbaudīt tokenu no saites
if ($tokenAusLink == $gespeichertesToken) { /* nedroši */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* droši */ }
// Pārbaudīt paroli
if (md5($eingabe) == $hash) { /* nedroši: vāja metode un brīva salīdzināšana */ }
if (password_verify($eingabe, $hash)) { /* droši */ }
// Meklēt apakšvirkni
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }
hash_equals() saskaņā ar rokasgrāmatu kā pirmo parametru gaida zināmo noslēpumu un kā otro lietotāja vērtību un salīdzina nemainīgā laikā. Kā tokenus vispār droši izveidot, apraksta Droši gadījuma skaitļi un tokeni PHP.
declare(strict_types=1) salīdzinājumus nepadara stingrus. Šī instrukcija attiecas tikai uz tipu pārveidi funkciju izsaukumos un atgriežamajās vērtībās. Brīvie salīdzinājumi kodā jāaizstāj pašiem.
Stingriem salīdzinājumiem ir otra puse: tie neizdodas, ja tipi nesakrīt. '42' === 42 ir nepatiess. Vērtības no veidlapām un URL PHP ierodas kā virknes, vērtības no datubāzes atkarībā no draivera un iestatījuma kā virknes vai skaitļi. Kas visur ievieš ===, neizlīdzinot tipus, tas rada kļūdas, kas ekspluatācijā izpaužas kā pamatotu lietotāju noraidīšana.
Risinājums ir skaidra robeža: ievade tiek validēta saņemšanas brīdī un pārvērsta pareizajā tipā, piemēram, ar filter_var($wert, FILTER_VALIDATE_INT), un pēc tam kodā apstrādāta tikai ar tipu deklarācijām un stingriem salīdzinājumiem. Tipu deklarācijas parametriem, atgriezumiem un īpašībām palīdz tādēļ, ka nepareizi tipi tad agri izpaužas ar kļūdas ziņojumu, nevis kluso pazūd salīdzinājumā.
Esošā koda sistemātiska pārbaude
Izaugušos projektos brīvo salīdzinājumu ir simtiem. Ne katrs ir problēma. Koncentrējieties uz vietām, kur tiek pieņemts lēmums par piekļuvi:
# Brīvie salīdzinājumi rindās ar drošībai svarīgiem jēdzieniem
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array bez stingrā salīdzinājuma
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
Statiskās analīzes rīki, piemēram, PHPStan vai Psalm, pēc vēlēšanās var brīvos salīdzinājumus ziņot pastāvīgi. Tā jauni gadījumi vispār nenonāk galvenajā zarā. Pamatus par tipiem apraksta PHP tipu sistēma; kā ievadi iepriekš pārvērst pareizajā tipā, rāda Ievades validēšana un filtrēšana PHP.
Ko tas nozīmē vecām sistēmām
Lietojumprogrammas, kas vēl darbojas ar PHP 7, skar vecie, brīvākie salīdzināšanas noteikumi. Klāt nāk otra problēma: PHP 8.1 un vecākām versijām PHP projekts oficiālus drošības atjauninājumus vairs nesniedz, arī ne pret PHP2Shell. Situāciju apraksta PHP 8.1 un vecākas: kas tagad spēkā.
Migrējot uz PHP 8, daži salīdzinājumu rezultāti mainās. Tas parasti ir uzlabojums, bet var salauzt kodu, kas paļāvies uz veco uzvedību. Tāpēc plānojiet testus, kas mērķtiecīgi aptver salīdzinājumus ar skaitļiem, tukšām vērtībām un null.
Kā PHP SOS palīdz konkrēti?
- Mēs pārmeklējam jūsu kodu uz brīvajiem salīdzinājumiem vietās, kas lemj par pieteikšanos, tiesībām vai maksājumiem.
- Mēs tos aizstājam ar stingriem salīdzinājumiem,
matchizteiksmēm un piemērotām pārbaudes funkcijām. - Mēs iestatām statisko analīzi tā, lai jauni brīvie salīdzinājumi izpaustos būvējumā.
- Mēs pavadām migrācijas uz aktuālajām PHP versijām ar testiem mainītajiem salīdzināšanas noteikumiem.
Jūsu programmatūras pārbaudi apraksta drošības audits un penetrācijas tests.
Biežāk uzdotie jautājumi
Kas ir Type Juggling PHP?
PHP dažās darbībās dažādu tipu vērtības pārveido automātiski, piemēram, virkni par skaitli. Brīvā salīdzinājumā ar == šī pārveide nosaka rezultātu. Tas ir ērti, bet var novest pie salīdzinājumiem, kas ir patiesi, kaut gan vērtības neizskatās vienādas.
Vai PHP 8 problēmu atrisināja?
Daļēji. Kopš PHP 8.0 skaitli ar nenumerisku virkni salīdzina kā virknes, tātad 0 == 'a' vairs nav patiess. Tomēr divas numeriskas virknes joprojām salīdzina kā skaitļus, un salīdzinājumiem ar true, false un null ir savi noteikumi.
Vai declare(strict_types=1) palīdz pret brīvajiem salīdzinājumiem?
Nē. strict_types nosaka, vai skalārie tipi funkciju izsaukumos un atgriezumos tiek pārveidoti automātiski. Uz salīdzināšanas operatoriem == un != tam ietekmes nav.
Kā pareizi salīdzināt paroles un tokenus?
Paroles pārbaudiet ar password_verify, tokenus un parakstus ar hash_equals. Abas salīdzina precīzi, un hash_equals nemainīgā laikā. Salīdzinājums ar == vai strcmp tam nav piemērots.