Lühidalt: PHP unserialize turvalisuse puhul kehtib lihtne reegel: andmeid, mis tulevad väljastpoolt, ei töödelda kunagi käsuga unserialize(). Kasutage selle asemel JSON-it. Kus unserialize() peab vanast koodist alles jääma, piirake seda valikuga allowed_classes => false ja kontrollige andmete ehtsust allkirjaga.
Mis võib unserialize() puhul valesti minna
PHP saab massiive ja objekte käsuga serialize() märgijadaks muuta ning käsuga unserialize() taastada. See on mugav andmete hoidmiseks küpsistes, vahemälus või andmebaasiväljadel. Probleem: unserialize() loob seejuures ka suvaliste klasside objekte, mida rakenduses saab laadida.
Sellise objekti loomisel ja hiljemalt hävitamisel kutsub PHP automaatselt välja teatud meetodeid, näiteks __wakeup(), __unserialize() või __destruct(). Kui projekti või teekide klassid sisaldavad selliseid meetodeid fail-, andmebaasi- või süsteemitoimingutega, saab ründaja neid manipuleeritud andmete kaudu väärkasutada. Asjatundjad räägivad PHP Object Injectionist. Olemasolevatest klassidest olenevalt ulatub mõju üksikute failide kustutamisest kahjuliku koodi käivitamiseni.
Mittetehnikutele kokkuvõtlikult: üks ainus läbimõtlemata väljakutse vales kohas võib lasta ründaja rakenduses kaugele. Seetõttu kuulub see nõrkus aastaid OWASP Top 10 teemade hulka.
Turvaline alternatiiv: JSON unserialize asemel
Peaaegu kõigil juhtudel, kui andmed tulevad väljastpoolt või serverist lahkuvad, on JSON õige valik. json_decode() loob ainult massiive, arve, märgijadasid ja tõeväärtusi, mitte oma klasside objekte.
<?php
declare(strict_types=1);
function leseEinstellungen(string $eingabe): array
{
try {
$daten = json_decode($eingabe, true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new InvalidArgumentException('Ungültige Daten.');
}
if (!is_array($daten)) {
throw new InvalidArgumentException('Erwartet wurde ein Objekt.');
}
// Võtta üle ainult tuntud väljad oodatud tüüpidega
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Oluline on teine osa: ka JSON-andmed on sisend. Võtke üle ainult oodatud väljad ja kontrollige tüüpe ning väärtusvahemikke, enne kui nendega töötate.
Kui unserialize on vältimatu: allowed_classes ja allkiri
Vanemates rakendustes on serialiseeritud andmed sageli andmebaasides või vahemälus, mida ei saa kohe ümber seada. Siis piirate riski kahe sammuga. Esiteks takistate valikuga allowed_classes objektide loomist. Teiseks tagate HMAC-allkirjaga, et andmed pärinevad tõesti teie rakendusest ja neid ei ole teel muudetud.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // mitte kunagi koodihoidlas
function verpacken(array $daten): string
{
$roh = serialize($daten);
return hash_hmac('sha256', $roh, SCHLUESSEL) . '.' . base64_encode($roh);
}
function auspacken(string $paket): array
{
[$signatur, $b64] = array_pad(explode('.', $paket, 2), 2, '');
$roh = base64_decode($b64, true);
if ($roh === false || !hash_equals(hash_hmac('sha256', $roh, SCHLUESSEL), $signatur)) {
throw new RuntimeException('Signatur ungültig.');
}
$daten = unserialize($roh, ['allowed_classes' => false, 'max_depth' => 16]);
if (!is_array($daten)) {
throw new RuntimeException('Unerwartetes Format.');
}
return $daten;
}
Kui teatud klasse siiski vajatakse, nimetage need allowed_classes all eraldi, selle asemel et lubada kõik. Võti kuulub serveri seadistusse või saladuste hoidlasse ja see tuleb pärast intsidenti uuendada.
Olemasolevate andmete järkjärguline üleminek JSON-ile
Kasvanud rakendustes asuvad serialiseeritud väärtused sageli aastaid andmebaasis. Ülemineku tegemine ühe sammuga on siis riskantne. Üleminek on end tõestanud nii: rakendus loeb mõlemat vormingut, kuid kirjutab enam ainult JSON-it. Vana väärtuse lugemisel pakitakse see ühekordselt valikuga allowed_classes => false lahti ja kirjutatakse kohe JSON-ina tagasi. Mõne aja pärast on enamik kirjeid ümber seatud ja ühekordne skript teeb ülejäänu ära.
Logige ülemineku ajal, kui sageli loetakse veel vana vormingut, ja ka siis, kui allkirjad ei klapi. Sellised tõrked võivad viidata manipuleerimiskatsetele ja kuuluvad seire alla. Alles kui vana vormingut enam ei esine, eemaldage unserialize() haru koodist täielikult.
Varjatud deserialiseerimine: sessioonid, vahemälud ja failiteed
Mitte iga deserialiseerimine ei ole oma koodis nähtav. Kontrollige ka neid kohti:
- Sessioonid: PHP serialiseerib sessiooniandmed automaatselt. Kuni ründajad sessioonifaile kirjutada ei saa, on see ohutu. Veebisaitide kaupa lahutatud sessiooniteed kirjeldab juhend Sessioonide turvamine PHP-s.
- Sessiooni serialiseerija: Märkus CVE-2026-115560 kohta puudutab ebatavalist seadistust, kus
serialize_handlerlülitatakse tööajal käsugaini_set()ümber ja sessiooni üleslaadimise edenemine on aktiivne. Leevendus php.net-i järgi:session.upload_progress.enabled=0php.ini-s või FPM-i poolis, seejärel töötajate taaskäivitus. - Vahemälud ja järjekorrad: Redis, Memcached või andmebaasitabelid serialiseeritud väärtustega on sama turvalised kui juurdepääs neile. Need ei kuulu avalikult võrku.
- Failifunktsioonid päringust pärit teedega: Ärge andke failifunktsioonidele kunagi päringust pärit teid. Voo ümbrised (stream wrapper) nagu
phar://võivad muidu käivitada ootamatu töötluse. Failinimede lubatud nimekiri kaitseb siin usaldusväärsemalt kui ükski filtreerimine. - Sõltuvused: Teadaolevate nõrkustega teegid laiendavad ründaja võimalusi. Hoidke need ajakohasena, nagu on kirjeldatud juhendis Composeri sõltuvuste kontroll turvaaukude suhtes.
Riskantsete kohtade leidmine koodist
Esimese inventuuri saab lihtsa otsinguga. See ei muuda süsteemis midagi ja näitab kõiki väljakutseid väljaspool kataloogi vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Hinnake iga tabamust kolme küsimusega: kust andmed tulevad? Kas kasutaja saab neid muuta? Kas allowed_classes on seatud? Tabamused küpsistest, $_GET, $_POST, $_REQUEST või väliste liideste sisendiga on eelisjärjekorras. Asendage need JSON-iga ja testige üleminekut enne selle väljarullimist.
Veel üks tugipunkt asub brauseris. Serialiseeritud PHP andmetel on tüüpiline ülesehitus tüübitunnuste ja pikkusenäitajatega. Kui leiate selliseid väärtusi küpsistest, peidetud vormiväljadelt või oma veebisaidi parameetritest, on rakendus andnud külastajale serialiseeritud andmeid ja ootab neid ilmselt tagasi. Just neid kohti tuleb kontrollida.
Juhatusele piisab lihtsast küsimusest arendajatele või agentuurile: „Kas meie rakendus töötleb kuskil väljastpoolt tulevaid serialiseeritud andmeid ja kui jah, kuidas need on kaitstud?“ Selge vastus koos leiukohtadega on hea märk. Kõrvalepõikav vastus on põhjus lasta rakendus üle kontrollida.
Kuidas PHP SOS konkreetselt aitab?
- Otsime teie koodist deserialiseerimist võõraste andmetega ja hindame iga leidu kättesaadavuse ja mõju järgi.
- Pakume konkreetseid üleminekuid JSON-ile või turvatud väljakutsetele ja toetame teie arendajaid teostuses.
- Kontrollime sõltuvusi teadaolevate nõrkuste suhtes ja dokumenteerime seisu.
- Soovi korral laseme rakenduse turvaauditi ja penetratsiooniteski raames sõltumatult üle kontrollida.
Korduma kippuvad küsimused
Kas unserialize() on PHP-s põhimõtteliselt ebaturvaline?
Mitte andmete puhul, mida loob ainult teie enda rakendus ja mida keegi muuta ei saa. Ohtlikuks muutub see niipea, kui kasutajad või kolmandad osapooled saavad sisendit mõjutada, näiteks küpsiste, vormiväljade või väliste liideste kaudu.
Kas allowed_classes => false kaitseks piisab?
See takistab suvaliste objektide loomist ja sulgeb sellega tähtsaima ründetee. Väljastpoolt tuleva sisendi jaoks on JSON siiski parem valik, sest parser on lihtsam ega taasta objekte.
Kas teekide uuendamine aitab deserialiseerimisrünnakute vastu?
Osaliselt. Rünnakud kasutavad sageli sõltuvuste klasse, mis objekti hävitamisel või ärkamisel toiminguid sooritavad. Värsked paketid sulgevad teadaolevad ahelad, kuid tegelik põhjus on unserialize väljakutse võõraste andmetega.
Kas PHP2Shellil on deserialiseerimisega midagi pistmist?
Kaks põhiturvaauku asuvad PHP päringutöötluses. Märkus CVE-2026-115560 kohta puudutab aga sessioonide serialiseerimise ebatavalist seadistust. Leevendus on seal session.upload_progress.enabled=0.