Röviden: A PHP type juggling az automatikus típusátalakítást jelenti, amely a == operátorral végzett laza összehasonlításnál dönti el az eredményt. A PHP 8.0 óta a szabályok szigorúbbak, de a numerikus karakterláncokat továbbra is számként hasonlítja össze, a true, false és null pedig saját szabályokat követ. A biztonsági szempontból fontos ellenőrzésekre ezért ez érvényes: ===, in_array(..., true), match a switch helyett, hash_equals() a tokenekhez és password_verify() a jelszavakhoz.
Egy összehasonlítás, amely igaz, noha nem kellene annak lennie
A PHP-alkalmazások sok biztonsági rése nem bonyolult függvényekben rejlik, hanem egyetlen karakterben: egy ==-ben, ahová ===-nek kellett volna kerülnie. A kód helyesnek látszik, a normál bemenetekkel végzett tesztek lefutnak, és az ellenőrzés mégis bizonyos feltételek mellett átenged valamit, amit meg kellene állítania.
Az ok a PHP egy tulajdonsága, amely a mindennapokban praktikus: az értékeket szükség esetén más típusra alakítja. Egy űrlapból származó karakterlánc így kerülőút nélkül összehasonlítható egy adatbázisból származó számmal. Bejelentkezéseknél, tokeneknél, szerepköröknél vagy állapotmezőknél éppen ez a nagyvonalúság válhat veszélyessé.
Hogyan hasonlít a PHP a == operátorral
A PHP kézikönyve részletesen leírja a laza összehasonlítások szabályait. A biztonság szempontjából főleg ezek az esetek fontosak:
| Összehasonlítás | PHP 7 | PHP 8 | Ok |
|---|---|---|---|
0 == "a" |
igaz | hamis | a PHP 8.0 óta karakterláncként hasonlítja össze |
"1" == "01" |
igaz | igaz | mindkettő numerikus, összehasonlítás számként |
"10" == "1e1" |
igaz | igaz | az exponenciális írásmód numerikus |
100 == "1e2" |
igaz | igaz | szám numerikus karakterlánccal szemben |
null == false |
igaz | igaz | összehasonlítás logikai értékként |
"abc" == true |
igaz | igaz | a nem üres karakterlánc igaznak számít |
A PHP 8.0-ban bekövetkezett változás a hibák egy egész osztályát tompította: korábban egy nem numerikus karakterlánc egy számmal való összehasonlításnál 0-vá vált. Ez a switch utasításokat is érintette. A táblázat többi sora viszont azt mutatja, hogy a laza összehasonlítások továbbra is tartogatnak meglepetéseket.
A <, > operátorok és a <=> összehasonlító operátor is ezeket a szabályokat követi. Rendezéseknél ez többnyire ártalmatlan, a „összeg kisebb a limitnél” típusú ellenőrzéseknél, a kérésből származó értékekkel, a típusnak előre rögzítettnek kell lennie.
Különösen alattomos az exponenciális írásmódú sor. A hash-értékek és a tokenek gyakran számjegyekből és betűkből állnak. Ha egy ilyen érték véletlenül úgy néz ki, mint egy exponenciális írásmódú szám, a == két különböző hasht esetleg egyformának hasonlít össze. Ez a háttere a régebbi PHP-alkalmazások bejelentkezés-megkerüléseiről szóló sok beszámolónak.
PHP type juggling tipikus biztonsági ellenőrzésekben
A laza összehasonlítások több helyen rejtőznek, mint csak a == operátorban:
- Az
in_array()és azarray_search()harmadik paraméter nélkül lazán hasonlít. A szerepkörök vagy azonosítók engedélyezési listája ezáltal lazább a gondoltnál. - A
switchlazán hasonlít. A PHP 8.0 óta elérhetőmatchkifejezés a kézikönyv szerint ezzel szemben típusbiztosan,===-vel hasonlít, és hibát dob, ha egyik ág sem illik. - A JSON-bemenetek valódi típusokat szolgáltatnak. Egy mező, amelyet karakterláncként vár,
true-ként vagy számként is megérkezhet, és a==szerint meglepően egyezhet. - A
strcmp()titkokhoz alkalmatlan, mert a futási idő a tartalomtól függ, és a hibaeseteket könnyen rosszul értékelik ki. - A visszatérési értékek ellenőrzése: az olyan függvények, mint a
strpos(),0-t adnak az elején lévő találatra, ésfalse-t a találat hiányára. Azif (!strpos(...))összekeveri a kettőt.
Biztonságos összehasonlítások: a szembeállítás
A következő példák mindegyike a nem biztonságos és a biztonságos változatot mutatja:
<?php
declare(strict_types=1);
// Szerepkör ellenőrzése
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen); // nem biztonságos: laza
in_array($rolle, $erlaubteRollen, true); // biztonságos: szigorú
// Állapot kiértékelése
$aktion = match ($status) { // szigorú, ismeretlen értéknél hiba
'offen' => 'bearbeiten',
'erledigt' => 'archivieren',
};
// Token ellenőrzése a linkből
if ($tokenAusLink == $gespeichertesToken) { /* nem biztonságos */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* biztonságos */ }
// Jelszó ellenőrzése
if (md5($eingabe) == $hash) { /* nem biztonságos: gyenge eljárás és laza összehasonlítás */ }
if (password_verify($eingabe, $hash)) { /* biztonságos */ }
// Részstring keresése
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }
A hash_equals() a kézikönyv szerint a ismert titkot várja első, a felhasználó értékét második paraméterként, és állandó időben hasonlít. Hogyan hozhat létre egyáltalán biztonságosan tokeneket, azt a Biztonságos véletlenszámok és tokenek PHP-ban írja le.
A declare(strict_types=1) nem teszi szigorúvá az összehasonlításokat. Az utasítás csak a függvényhívásoknál és a visszatérési értékeknél történő típusátalakítást érinti. A laza összehasonlításokat Önnek kell lecserélnie a kódban.
A szigorú összehasonlításoknak van egy hátoldaluk: elbuknak, ha a típusok nem illenek össze. A '42' === 42 hamis. Az űrlapokból és URL-ekből származó értékek a PHP-ban karakterláncként érkeznek, az adatbázisból származók a meghajtótól és a beállítástól függően karakterláncként vagy számként. Aki mindenhol ===-t vezet be a típusok kiigazítása nélkül, olyan hibákat okoz, amelyek üzem közben jogos felhasználók elutasításaként tűnnek fel.
A megoldás egy világos határ: a bemeneteket fogadáskor validálja, és a megfelelő típusra hozza, például a filter_var($wert, FILTER_VALIDATE_INT) segítségével, majd a kódban már csak típusdeklarációkkal és szigorú összehasonlításokkal kezelje. A paraméterek, visszatérési értékek és tulajdonságok típusdeklarációi ebben segítenek, mert a hibás típusok így korán hibaüzenettel tűnnek fel, ahelyett hogy csendben eltűnnének egy összehasonlításban.
A meglévő kód szisztematikus ellenőrzése
A gyarapodott projektekben a laza összehasonlítások százával fordulnak elő. Nem mindegyik probléma. Összpontosítson azokra a helyekre, ahol a hozzáférésről döntenek:
# Laza összehasonlítások biztonsági szempontból releváns kifejezéseket tartalmazó sorokban
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array szigorú összehasonlítás nélkül
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"
A statikus elemzés eszközei, mint a PHPStan vagy a Psalm, kérésre tartósan jelezhetik a laza összehasonlításokat. Így az új esetek egyáltalán nem kerülnek be a főágba. A típusok alapjait a A PHP típusrendszere írja le; hogyan hozza előre a megfelelő típusra a bemeneteket, azt a Bemenetek validálása és szűrése PHP-ban mutatja.
Mit jelent ez a régi rendszereknél
Azokat az alkalmazásokat, amelyek még PHP 7-en futnak, a régi, lazább összehasonlítási szabályok érintik. Ehhez jön egy második probléma: a PHP 8.1-re és régebbiekre a PHP-projekt nem ad több hivatalos biztonsági frissítést, a PHP2Shell ellen sem. A helyzetet a PHP 8.1 és régebbiek: mi érvényes most írja le.
A PHP 8-ra való migrációnál egyes összehasonlítási eredmények megváltoznak. Ez többnyire javulás, de elronthat olyan kódot, amely a régi viselkedésre támaszkodott. Ezért tervezzen olyan teszteket, amelyek célzottan lefedik a számokkal, üres értékekkel és null-lal végzett összehasonlításokat.
Hogyan segít konkrétan a PHP SOS?
- Átvizsgáljuk a kódját laza összehasonlítások után azokon a helyeken, amelyek a bejelentkezésről, a jogokról vagy a fizetésekről döntenek.
- Lecseréljük őket szigorú összehasonlításokra,
matchkifejezésekre és megfelelő ellenőrző függvényekre. - Úgy állítjuk be a statikus elemzést, hogy az új laza összehasonlítások feltűnjenek a buildben.
- A korszerű PHP-verziókra való migrációkat a megváltozott összehasonlítási szabályokra írt tesztekkel kísérjük.
A szoftver ellenőrzését a biztonsági audit és penetrációs teszt írja le.
Gyakori kérdések
Mi a type juggling PHP-ban?
A PHP egyes műveleteknél automatikusan átalakítja a különböző típusú értékeket, például egy karakterláncot számmá. A == operátorral végzett laza összehasonlításnál ez az átalakítás dönti el az eredményt. Ez kényelmes, de olyan összehasonlításokhoz vezethet, amelyek igazak, noha az értékek nem egyformák.
Megoldotta a PHP 8 a problémát?
Részben. A PHP 8.0 óta egy számot egy nem numerikus karakterlánccal karakterláncként hasonlít össze, így a 0 == 'a' már nem igaz. Két numerikus karakterláncot viszont továbbra is számként hasonlít össze, a true, false és null értékekkel végzett összehasonlítások pedig saját szabályokat követnek.
Segít a declare(strict_types=1) a laza összehasonlítások ellen?
Nem. A strict_types azt szabályozza, hogy a skaláris típusokat átalakítják-e automatikusan a függvényhívásoknál és a visszatérési értékeknél. A == és != összehasonlító operátorokra nincs hatása.
Hogyan hasonlítsam össze helyesen a jelszavakat és a tokeneket?
A jelszavakat a password_verify-vel ellenőrizze, a tokeneket és aláírásokat a hash_equals-szal. Mindkettő pontosan hasonlít, a hash_equals pedig állandó időben. A == vagy a strcmp ehhez alkalmatlan.