Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Type Juggling PHP:ssä: löyhät vertailut tietoturvariskinä

Miksi löyhät vertailut operaattorilla == voivat PHP:ssä johtaa tietoturva-aukkoihin, mitä PHP 8.0 muutti ja miten kirjoitatte vertailut kirjautumisissa, tokeneissa ja käyttöoikeuksissa kestävästi.

Tilanne Lukuaika 4 minTurvallinen kehitys

Lyhyesti: PHP Type Juggling tarkoittaa automaattista tyyppimuunnosta, joka ratkaisee tuloksen löyhässä vertailussa operaattorilla ==. PHP 8.0:sta alkaen säännöt ovat tiukempia, mutta numeerisia merkkijonoja vertaillaan yhä lukuina, ja true, false ja null noudattavat omia sääntöjään. Tietoturvan kannalta olennaisissa tarkistuksissa pätee siksi: ===, in_array(..., true), match switchin sijaan, hash_equals() tokeneille ja password_verify() salasanoille.

Vertailu, joka on tosi, vaikka sen ei pitäisi olla

Monet PHP-sovellusten tietoturva-aukot eivät piile monimutkaisissa funktioissa vaan yhdessä merkissä: == siellä, minne olisi kuulunut ===. Koodi näyttää oikealta, testit tavallisilla syötteillä menevät läpi, ja silti tarkistus päästää tietyissä olosuhteissa läpi jotain, minkä sen pitäisi pysäyttää.

Syy on PHP:n ominaisuus, joka on arjessa käytännöllinen: arvot muunnetaan tarvittaessa toiseen tyyppiin. Lomakkeen merkkijonoa voidaan siten vertailla suoraan tietokannan lukuun. Kirjautumisissa, tokeneissa, rooleissa tai tilakentissä juuri tämä sallivuus voi olla vaarallista.

Miten PHP vertaa operaattorilla ==

PHP-käsikirja kuvaa löyhien vertailujen säännöt tarkasti. Tietoturvan kannalta tärkeimpiä ovat nämä tapaukset:

Vertailu PHP 7 PHP 8 Syy
0 == "a" tosi epätosi PHP 8.0:sta alkaen vertaillaan merkkijonoina
"1" == "01" tosi tosi molemmat numeerisia, vertailu lukuna
"10" == "1e1" tosi tosi eksponenttimuoto on numeerinen
100 == "1e2" tosi tosi luku numeerista merkkijonoa vastaan
null == false tosi tosi vertailu totuusarvona
"abc" == true tosi tosi ei-tyhjä merkkijono katsotaan todeksi

PHP 8.0:n muutos lievensi kokonaisen virheluokan: aiemmin ei-numeerinen merkkijono muuttui vertailussa lukuun nähden arvoksi 0. Tämä koski myös switch-lauseita. Taulukon muut rivit osoittavat kuitenkin, että löyhät vertailut pitävät yhä sisällään yllätyksiä.

Myös operaattorit <, > ja vertailuoperaattori <=> noudattavat näitä sääntöjä. Lajitteluissa se on yleensä harmitonta, mutta tarkistuksissa kuten ”summa pienempi kuin raja” pyynnöstä tulevilla arvoilla tyypin pitäisi olla ennalta selvä.

Erityisen salakavala on eksponenttimuotoa koskeva rivi. Tiivisteet ja tokenit koostuvat usein numeroista ja kirjaimista. Jos tällainen arvo näyttää sattumalta eksponenttimuotoiselta luvulta, == saattaa vertailla kahta eri tiivistettä yhtäläisinä. Tämä on tausta monille raporteille kirjautumisen ohituksista vanhemmissa PHP-sovelluksissa.

PHP Type Juggling tyypillisissä tietoturvatarkistuksissa

Löyhät vertailut piilevät useammassa kohdassa kuin pelkässä operaattorissa ==:

  1. in_array() ja array_search() vertaavat ilman kolmatta parametria löyhästi. Rooleja tai tunnisteita sisältävä sallittujen luettelo on siten pehmeämpi kuin luulisi.
  2. switch vertaa löyhästi. PHP 8.0:sta saatavilla oleva lauseke match vertaa sen sijaan käsikirjan mukaan tyyppiturvallisesti operaattorilla === ja heittää virheen, jos mikään haara ei sovi.
  3. JSON-syötteet tuottavat todellisia tyyppejä. Kenttä, jonka odotatte olevan merkkijono, voi saapua arvona true tai lukuna ja täsmätä yllättäen operaattorilla ==.
  4. strcmp() salaisuuksille ei sovellu, koska ajoaika riippuu sisällöstä ja virhetapaukset arvioidaan helposti väärin.
  5. Paluuarvojen tarkistus: Funktiot kuten strpos() palauttavat 0 osumasta alussa ja false, jos osumaa ei ole. if (!strpos(...)) sekoittaa nämä.

Turvalliset vertailut: vastakkainasettelu

Seuraavat esimerkit näyttävät kussakin turvattoman ja turvallisen version:

<?php
declare(strict_types=1);

// Tarkista rooli
$erlaubteRollen = ['admin', 'redaktion'];
in_array($rolle, $erlaubteRollen);          // turvaton: löyhä
in_array($rolle, $erlaubteRollen, true);    // turvallinen: tiukka

// Arvioi tila
$aktion = match ($status) {                 // tiukka, virhe tuntemattomalla arvolla
    'offen'      => 'bearbeiten',
    'erledigt'   => 'archivieren',
};

// Tarkista linkin token
if ($tokenAusLink == $gespeichertesToken) { /* turvaton */ }
if (is_string($tokenAusLink) && hash_equals($gespeichertesToken, $tokenAusLink)) { /* turvallinen */ }

// Tarkista salasana
if (md5($eingabe) == $hash) { /* turvaton: heikko menetelmä ja löyhä vertailu */ }
if (password_verify($eingabe, $hash)) { /* turvallinen */ }

// Etsi osamerkkijono
if (strpos($text, 'abc') !== false) { /* korrekt: auch Treffer an Position 0 */ }

hash_equals() odottaa käsikirjan mukaan tunnetun salaisuuden ensimmäisenä ja käyttäjän arvon toisena parametrina ja vertaa vakioajassa. Miten tokenit ylipäätään luodaan turvallisesti, kuvaa Turvalliset satunnaisluvut ja tokenit PHP:ssä.

Tärkeää

declare(strict_types=1) ei tee vertailuista tiukkoja. Lause koskee vain tyyppimuunnosta funktiokutsuissa ja paluuarvoissa. Löyhät vertailut teidän on korvattava koodissa itse.

Tiukoilla vertailuilla on kääntöpuolensa: ne epäonnistuvat, jos tyypit eivät täsmää. '42' === 42 on epätosi. Lomakkeiden ja URL-osoitteiden arvot saapuvat PHP:ssä merkkijonoina, tietokannan arvot ajurista ja asetuksista riippuen merkkijonoina tai lukuina. Joka ottaa kaikkialla käyttöön === yhdenmukaistamatta tyyppejä, aiheuttaa virheitä, jotka huomataan käytössä oikeutettujen käyttäjien hylkäämisenä.

Ratkaisu on selkeä raja: syötteet validoidaan vastaanotettaessa ja muunnetaan oikeaan tyyppiin, esimerkiksi filter_var($wert, FILTER_VALIDATE_INT), ja käsitellään sen jälkeen koodissa vain tyyppimäärittelyillä ja tiukoilla vertailuilla. Tyyppimäärittelyt parametreissa, paluuarvoissa ja ominaisuuksissa auttavat tässä, koska väärät tyypit paljastuvat silloin varhain virheilmoituksella sen sijaan, että ne katoaisivat hiljaa vertailuun.

Vanhan koodin järjestelmällinen tarkistus

Vuosien mittaan kasvaneissa projekteissa löyhiä vertailuja on satoja. Kaikki eivät ole ongelma. Keskittykää kohtiin, joissa tehdään päätös pääsystä:

# Löyhät vertailut riveillä, joilla on tietoturvaan liittyviä termejä
grep -rnE "[^=!]==[^=]" src/ --include="*.php" | grep -iE "token|hash|pass|role|rolle|admin|auth|sign"
# in_array ilman tiukkaa vertailua
grep -rnE "in_array\([^)]*\)" src/ --include="*.php" | grep -v ", true)"

Staattisen analyysin työkalut kuten PHPStan tai Psalm voivat halutessa ilmoittaa löyhistä vertailuista pysyvästi. Näin uudet tapaukset eivät pääse edes pääkehityshaaraan. Tyyppien perusteet kuvaa PHP:n tyyppijärjestelmä; miten syötteet saatetaan ensin oikeaan tyyppiin, näyttää Syötteiden validointi ja suodatus PHP:ssä.

Mitä tämä tarkoittaa vanhoille järjestelmille

Sovellukset, jotka toimivat yhä PHP 7:llä, ovat vanhojen, löyhempien vertailusääntöjen kohteena. Lisäksi on toinen ongelma: PHP 8.1:lle ja sitä vanhemmille PHP-projekti ei enää julkaise virallisia tietoturvapäivityksiä, ei myöskään PHP2Shelliä vastaan. Tilanteen kuvaa PHP 8.1 ja vanhemmat: mikä nyt pätee.

PHP 8:aan migroitaessa jotkin vertailutulokset muuttuvat. Se on yleensä parannus, mutta voi rikkoa koodin, joka on luottanut vanhaan käyttäytymiseen. Suunnitelkaa siksi testit, jotka kattavat kohdennetusti vertailut luvuilla, tyhjillä arvoilla ja arvolla null.

Miten PHP SOS auttaa konkreettisesti?

  • Käymme koodinne läpi löyhien vertailujen varalta kohdissa, jotka ratkaisevat kirjautumisesta, oikeuksista tai maksuista.
  • Korvaamme ne tiukoilla vertailuilla, match-lausekkeilla ja sopivilla tarkistusfunktioilla.
  • Otamme staattisen analyysin käyttöön niin, että uudet löyhät vertailut havaitaan buildissa.
  • Tuemme migraatioita ajantasaisiin PHP-versioihin testeillä muuttuneille vertailusäännöille.

Ohjelmistonne tarkistuksen kuvaa tietoturva-auditointi ja tunkeutumistesti.

Usein kysytyt kysymykset

Mitä on Type Juggling PHP:ssä?

PHP muuntaa eri tyyppisiä arvoja automaattisesti joissakin operaatioissa, esimerkiksi merkkijonon luvuksi. Löyhässä vertailussa operaattorilla == tämä muunnos ratkaisee tuloksen. Se on kätevää, mutta voi johtaa vertailuihin, jotka ovat tosia, vaikka arvot eivät näytä samoilta.

Onko PHP 8 ratkaissut ongelman?

Osittain. PHP 8.0:sta alkaen luku ja ei-numeerinen merkkijono vertaillaan merkkijonoina, joten 0 == 'a' ei ole enää tosi. Kaksi numeerista merkkijonoa vertaillaan kuitenkin edelleen lukuina, ja vertailuihin arvoilla true, false ja null pätevät omat säännöt.

Auttaako declare(strict_types=1) löyhiä vertailuja vastaan?

Ei. strict_types ohjaa, muunnetaanko skalaarityypit automaattisesti funktiokutsuissa ja paluuarvoissa. Vertailuoperaattoreihin == ja != sillä ei ole vaikutusta.

Miten vertailen salasanoja ja tokeneita oikein?

Salasanat tarkistatte funktiolla password_verify, tokenit ja allekirjoitukset funktiolla hash_equals. Molemmat vertaavat täsmällisesti ja hash_equals vakioajassa. Vertailu operaattorilla == tai funktiolla strcmp ei sovellu tähän.

Lähteet

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu