Aktuális helyzet: PHP2Shell. Két kritikus PHP-rés (CVSS 9,2) lehetővé teszi kártékony kód futtatását bejelentkezés nélkül. Javítások 2026. 10. 21. óta.Mi a teendő most →

Type juggling PHP-ban: laza összehasonlítások mint biztonsági kockázat

Miért vezethetnek a == operátorral végzett laza összehasonlítások biztonsági résekhez PHP-ban, mi változott a PHP 8.0-val, és hogyan írja meg robusztusan az összehasonlításokat bejelentkezésekben, tokenekben és jogosultságokban.

Állapot 5 perc olvasási időBiztonságos fejlesztés

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:

  1. Az in_array() és az array_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.
  2. A switch lazán hasonlít. A PHP 8.0 óta elérhető match kifejezés a kézikönyv szerint ezzel szemben típusbiztosan, ===-vel hasonlít, és hibát dob, ha egyik ág sem illik.
  3. 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.
  4. A strcmp() titkokhoz alkalmatlan, mert a futási idő a tartalomtól függ, és a hibaeseteket könnyen rosszul értékelik ki.
  5. 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, és false-t a találat hiányára. Az if (!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.

Fontos

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, match kifejezé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.

Források

Azonnali kapcsolat

Minden óra számít. Írjon nekünk most.

Ismertesse röviden a rendszert, a PHP-verziót és a megfigyelését. Jelentkezünk, tisztázzuk a helyzetet, és távolról dolgozunk, amíg az incidens le nem zárul és dokumentálva nem lesz.

Azonnali segítség