Aktuellt läge: PHP2Shell. Två kritiska PHP-sårbarheter (CVSS 9,2) tillåter skadlig kod utan inloggning. Patchar sedan den 21 oktober 2026.Vad som bör göras nu →

Type juggling i PHP: lösa jämförelser som säkerhetsrisk

Varför lösa jämförelser med == i PHP kan leda till säkerhetsluckor, vad som ändrades med PHP 8.0 och hur ni skriver robusta jämförelser i inloggningar, tokens och behörigheter.

Aktualitet 5 min läsningSäker utveckling

Kort sagt: PHP type juggling betecknar den automatiska typomvandling som vid lös jämförelse med == avgör resultatet. Sedan PHP 8.0 är reglerna strängare, men numeriska strängar jämförs fortfarande som tal, och true, false och null följer egna regler. För säkerhetsrelevanta kontroller gäller därför: ===, in_array(..., true), match i stället för switch, hash_equals() för tokens och password_verify() för lösenord.

En jämförelse som är sann fast den inte borde vara det

Många säkerhetsluckor i PHP-applikationer sitter inte i komplicerade funktioner utan i ett enda tecken: ett == där ett === hade hört hemma. Koden ser korrekt ut, tester med normal inmatning går igenom, och ändå låter kontrollen under vissa förhållanden något ske som den skulle ha stoppat.

Orsaken är en egenskap hos PHP som är praktisk i vardagen: Värden omvandlas vid behov till en annan typ. En sträng från ett formulär kan på så sätt utan omväg jämföras med ett tal från databasen. Vid inloggningar, tokens, roller eller statusfält kan just den generositeten bli farlig.

Hur PHP jämför med ==

PHP-manualen beskriver reglerna för lösa jämförelser utförligt. För säkerheten är framför allt dessa fall viktiga:

Jämförelse PHP 7 PHP 8 Orsak
0 == "a" sant falskt jämförs som strängar sedan PHP 8.0
"1" == "01" sant sant båda numeriska, jämförelse som tal
"10" == "1e1" sant sant exponentialskrivsätt är numeriskt
100 == "1e2" sant sant tal mot numerisk sträng
null == false sant sant jämförelse som sanningsvärde
"abc" == true sant sant icke-tom sträng räknas som sann

Ändringen i PHP 8.0 har avgiftat en hel klass av fel: Tidigare blev en icke-numerisk sträng vid jämförelse med ett tal till 0. Det gällde även switch-satser. De övriga raderna i tabellen visar dock att lösa jämförelser fortfarande rymmer överraskningar.

Även operatorerna <, > och jämförelseoperatorn <=> följer de här reglerna. Vid sorteringar är det oftast ofarligt, men vid kontroller som ”belopp mindre än gräns” med värden från förfrågan bör typen vara fastställd i förväg.

Särskilt lömsk är raden med exponentialskrivsättet. Hashvärden och tokens består ofta av siffror och bokstäver. Ser ett sådant värde av en slump ut som ett tal i exponentialskrivsätt kan == jämföra två olika hashar som lika. Det är bakgrunden till många rapporter om kringgångna inloggningar i äldre PHP-applikationer.

PHP type juggling i typiska säkerhetskontroller

Lösa jämförelser gömmer sig på fler ställen än bara i operatorn ==:

  1. in_array() och array_search() jämför utan tredje parametern löst. En tillåtelselista med roller eller ID:n blir därigenom mjukare än tänkt.
  2. switch jämför löst. Uttrycket match, tillgängligt sedan PHP 8.0, jämför enligt manualen i stället typsäkert med === och kastar ett fel om ingen gren passar.
  3. JSON-inmatning ger riktiga typer. Ett fält som ni förväntar er som sträng kan komma som true eller som tal och överraskande stämma överens med ==.
  4. strcmp() för hemligheter är olämplig eftersom körtiden beror på innehållet och felfall lätt utvärderas fel.
  5. Kontroll av returvärden: Funktioner som strpos() returnerar 0 för en träff i början och false för ingen träff. if (!strpos(...)) förväxlar de båda.

Säkra jämförelser: motsättningen

Följande exempel visar vardera den osäkra och den säkra varianten:

<?php
declare(strict_types=1);

// Kontrollera roll
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // osäkert: löst
in_array($rolle, $erlaubteRollen, true);    // säkert: strikt

// Utvärdera status
$aktion = match ($status) {                 // strikt, fel vid okänt värde
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Kontrollera token från länk
if ($tokenAusLink == $gespeichertesToken) { /* osäkert */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* säkert */ }

// Kontrollera lösenord
if (md5($eingabe) == $hash) { /* osäker: svag metod och lös jämförelse */ }
if (password_verify($eingabe, $hash)) { /* säkert */ }

// Söka delsträng
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

hash_equals() förväntar sig enligt manualen den kända hemligheten som första och användarens värde som andra parameter och jämför i konstant tid. Hur ni över huvud taget skapar tokens säkert beskriver Säkra slumptal och tokens i PHP.

Viktigt

declare(strict_types=1) gör inte jämförelser strikta. Satsen berör bara typomvandlingen vid funktionsanrop och returvärden. Lösa jämförelser måste ni själva ersätta i koden.

Strikta jämförelser har en baksida: De misslyckas när typerna inte stämmer överens. '42' === 42 är falskt. Värden från formulär och URL:er kommer i PHP som strängar, värden från databasen beroende på drivrutin och inställning som strängar eller tal. Den som inför === överallt utan att jämna ut typerna skapar fel som i driften märks som avvisning av berättigade användare.

Lösningen är en tydlig gräns: Inmatning valideras vid mottagandet och omvandlas till rätt typ, till exempel med filter_var($wert, FILTER_VALIDATE_INT), och hanteras därefter i koden bara med typdeklarationer och strikta jämförelser. Typdeklarationer på parametrar, returvärden och egenskaper hjälper till, eftersom felaktiga typer då upptäcks tidigt med ett felmeddelande i stället för att tyst försvinna i en jämförelse.

Granska befintlig kod systematiskt

I projekt som vuxit över tid finns lösa jämförelser i hundratal. Inte alla är ett problem. Koncentrera er på ställen där ett beslut om åtkomst fattas:

# Lösa jämförelser på rader med säkerhetsrelevanta begrepp
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array utan strikt jämförelse
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Verktyg för statisk analys som PHPStan eller Psalm kan på begäran varaktigt rapportera lösa jämförelser. På så sätt hamnar nya fall inte ens i huvudgrenen. Grunderna om typer beskriver PHP:s typsystem; hur inmatning förs över till rätt typ i förväg visar Validera och filtrera inmatning i PHP.

Vad det innebär för äldre system

Applikationer som fortfarande körs på PHP 7 berörs av de gamla, slappare jämförelsereglerna. Dessutom finns ett andra problem: För PHP 8.1 och äldre finns inga officiella säkerhetsuppdateringar från PHP-projektet längre, inte heller mot PHP2Shell. Läget beskriver PHP 8.1 och äldre: vad som gäller nu.

Vid en migrering till PHP 8 ändras vissa jämförelseresultat. Det är oftast en förbättring men kan förstöra kod som har förlitat sig på det gamla beteendet. Planera därför in tester som specifikt täcker jämförelser med tal, tomma värden och null.

Hur hjälper PHP SOS konkret?

  • Vi genomsöker er kod efter lösa jämförelser på ställen som avgör inloggning, rättigheter eller betalningar.
  • Vi ersätter dem med strikta jämförelser, match-uttryck och lämpliga kontrollfunktioner.
  • Vi ställer in statisk analys så att nya lösa jämförelser upptäcks i bygget.
  • Vi följer migreringar till aktuella PHP-versioner med tester för ändrade jämförelseregler.

En granskning av er programvara beskriver säkerhetsaudit och penetrationstest.

Vanliga frågor

Vad är type juggling i PHP?

PHP omvandlar vid vissa operationer automatiskt värden av olika typ, till exempel en sträng till ett tal. Vid lös jämförelse med == avgör den omvandlingen resultatet. Det är bekvämt men kan leda till jämförelser som är sanna fast värdena inte ser lika ut.

Har PHP 8 löst problemet?

Delvis. Sedan PHP 8.0 jämförs ett tal med en icke-numerisk sträng som sträng, så 0 == 'a' är inte längre sant. Två numeriska strängar jämförs dock fortfarande som tal, och jämförelser med true, false och null följer egna regler.

Hjälper declare(strict_types=1) mot lösa jämförelser?

Nej. strict_types styr om skalära typer omvandlas automatiskt vid funktionsanrop och returvärden. På jämförelseoperatorerna == och != har det ingen inverkan.

Hur jämför jag lösenord och tokens rätt?

Lösenord kontrollerar ni med password_verify, tokens och signaturer med hash_equals. Båda jämför exakt och, hash_equals, i konstant tid. En jämförelse med == eller strcmp är olämplig för det.

Källor

Direktkontakt

Varje timme räknas. Skriv till oss nu.

Beskriv kort system, PHP-version och observation. Vi hör av oss, klarlägger läget och arbetar på distans tills incidenten är avslutad och dokumenterad.

Direkthjälp