Na kratko: Pri varnosti unserialize v PHP velja preprosto pravilo: podatkov, ki prihajajo od zunaj, nikoli ne obdelujte z unserialize(). Namesto tega uporabite JSON. Kjer mora unserialize() iz stare kode ostati, ga omejite z allowed_classes => false in pristnost podatkov preverite s podpisom.
Kaj lahko pri unserialize() gre narobe
PHP lahko tabele in objekte s serialize() pretvori v niz znakov in jih z unserialize() obnovi. To je udobno za shranjevanje podatkov v piškotke, predpomnilnike ali polja podatkovne zbirke. Težava: unserialize() pri tem ustvari tudi objekte poljubnih razredov, ki jih je v aplikaciji mogoče naložiti.
Pri ustvarjanju in najpozneje pri uničenju takega objekta PHP samodejno pokliče določene metode, na primer __wakeup(), __unserialize() ali __destruct(). Če razredi v projektu ali knjižnicah vsebujejo takšne metode z dejanji nad datotekami, podatkovno zbirko ali sistemom, jih lahko napadalec prek manipuliranih podatkov zlorabi. Strokovnjaki govorijo o PHP Object Injection. Glede na obstoječe razrede učinek sega od brisanja posameznih datotek do izvajanja škodljive kode.
Za netehnične bralce povzeto: en sam nepremišljen klic na napačnem mestu lahko napadalca spusti globoko v aplikacijo. Ta šibkost zato že leta sodi med teme OWASP Top 10.
Varna alternativa: JSON namesto unserialize
Za skoraj vse primere, ko podatki prihajajo od zunaj ali zapuščajo strežnik, je JSON prava izbira. json_decode() ustvari samo tabele, števila, nize in logične vrednosti, ne pa objektov lastnih razredov.
<?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.');
}
// Prevzemite samo znana polja s pričakovanimi tipi
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Pomemben je drugi del: tudi podatki JSON so vnosi. Prevzemite samo pričakovana polja ter preverite tipe in območja vrednosti, preden z njimi delate.
Če je unserialize neizogiben: allowed_classes in podpis
V starejših aplikacijah so serializirani podatki pogosto v podatkovnih zbirkah ali predpomnilnikih, ki jih ni mogoče takoj preusmeriti. Tedaj tveganje omejite v dveh korakih. Prvič, z allowed_classes preprečite ustvarjanje objektov. Drugič, s podpisom HMAC zagotovite, da podatki res izvirajo iz vaše aplikacije in na poti niso bili spremenjeni.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nikoli v repozitoriju kode
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;
}
Če so določeni razredi vendarle potrebni, jih v allowed_classes navedite posamično, namesto da bi dovolili vse. Ključ sodi v konfiguracijo strežnika ali shrambo skrivnosti in ga je treba po incidentu zamenjati.
Obstoječe podatke postopoma prevedite na JSON
Pri zraslih aplikacijah serializirane vrednosti v podatkovni zbirki pogosto ležijo leta. Preklop v enem koraku je tedaj tvegan. Obnesel se je prehod: aplikacija bere obe obliki, piše pa samo še JSON. Ob branju stare vrednosti se ta enkrat razpakira z allowed_classes => false in takoj zapiše nazaj kot JSON. Čez nekaj časa je večina vnosov preklopljenih, preostanek opravi enkratna skripta.
Med prehodom beležite, kako pogosto se še bere stara oblika, in tudi kdaj podpisi ne ustrezajo. Takšne neuspešne primere lahko kažejo na poskuse manipulacije in sodijo v nadzor. Šele ko se stara oblika ne pojavlja več, vejo unserialize() v celoti odstranite iz kode.
Skrita deserializacija: seje, predpomnilniki in poti datotek
Vsaka deserializacija ni vidna v lastni kodi. Preverite tudi ta mesta:
- Seje: PHP podatke sej serializira samodejno. Dokler napadalci datotek sej ne morejo pisati, je to nekritično. Ločene poti sej za vsako spletno stran opisuje vodnik Zaščita sej v PHP.
- Serializator sej: Opozorilo o CVE-2026-115560 zadeva nenavadno konfiguracijo, pri kateri se
serialize_handlermed izvajanjem preklopi zini_set()in je session upload progress aktiven. Rešitev po php.net:session.upload_progress.enabled=0v php.ini ali poolu FPM, nato ponovni zagon workerjev. - Predpomnilniki in čakalne vrste: Redis, Memcached ali tabele podatkovne zbirke s serializiranimi vrednostmi so varni toliko, kot je varen dostop do njih. Ne sodijo odprti v omrežje.
- Funkcije za datoteke s potmi iz vnosov: Funkcijam za datoteke nikoli ne predajajte poti iz zahteve. Ovojnice tokov, kot je
phar://, sicer lahko sprožijo nepričakovano obdelavo. Seznam dovoljenih imen datotek tu ščiti zanesljiveje od vsakega filtriranja. - Odvisnosti: Knjižnice z znanimi šibkostmi razširijo možnosti napadalca. Ohranjajte jih aktualne, kot opisuje vodnik Preverjanje odvisnosti Composer na ranljivosti.
Iskanje tveganih mest v kodi
Prvi popis uspe s preprostim iskanjem. Nič ne spremeni v sistemu in prikaže vse klice zunaj mape vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Vsak zadetek ocenite s tremi vprašanji: od kod prihajajo podatki? Ali jih lahko uporabnik spremeni? Je allowed_classes nastavljen? Zadetki z vnosi iz piškotkov, $_GET, $_POST, $_REQUEST ali zunanjih vmesnikov imajo prednost. Zamenjajte jih z JSON in preklop preizkusite, preden ga uvedete.
Dodatno izhodišče je v brskalniku. Serializirani podatki PHP imajo značilno zgradbo z oznakami tipov in navedbami dolžin. Če take vrednosti najdete v piškotkih, skritih poljih obrazcev ali parametrih lastne spletne strani, je aplikacija serializirane podatke predala obiskovalcu in jih verjetno pričakuje nazaj. Prav ta mesta je treba preveriti.
Za vodstvo zadošča preprosto vprašanje razvijalcem ali agenciji: „Ali naša aplikacija kje obdeluje serializirane podatke, ki prihajajo od zunaj, in če da, kako so zaščiteni?“ Jasen odgovor z navedbo mest je dober znak. Izmikajoč odgovor je razlog, da aplikacijo date preveriti.
Kako PHP SOS pomaga konkretno?
- Vašo kodo preiščemo glede deserializacije s tujimi podatki in vsako najdbo ocenimo po dosegljivosti in učinku.
- Predlagamo konkretne preklope na JSON ali zaščitene klice in vaše razvijalce spremljamo pri izvedbi.
- Odvisnosti preverimo glede znanih šibkosti in stanje dokumentiramo.
- Na željo aplikacijo neodvisno preverimo v okviru varnostnega pregleda in penetracijskega testa.
Pogosta vprašanja
Ali je unserialize() v PHP načelno nevaren?
Ne za podatke, ki jih ustvarja samo vaša lastna aplikacija in jih nihče ne more spremeniti. Nevarno postane, takoj ko lahko uporabniki ali tretje osebe vplivajo na vnos, na primer prek piškotkov, polj obrazcev ali zunanjih vmesnikov.
Ali zadošča allowed_classes => false kot zaščita?
Preprečuje, da bi se ustvarili poljubni objekti, in s tem zapre najpomembnejšo pot napada. Za vnose od zunaj je JSON kljub temu boljša izbira, ker je razčlenjevalnik preprostejši in ne rekonstruira objektov.
Ali posodobitev knjižnic pomaga proti napadom z deserializacijo?
Delno. Napadi pogosto izkoriščajo razrede iz odvisnosti, ki ob uničenju ali prebujanju objekta izvedejo dejanja. Aktualni paketi zaprejo znane verige, pravi vzrok pa je klic unserialize s tujimi podatki.
Ali ima PHP2Shell kaj opraviti z deserializacijo?
Obe glavni ranljivosti sta v obdelavi zahtev v PHP. Opozorilo o CVE-2026-115560 pa zadeva nenavadno konfiguracijo serializacije sej. Rešitev je tam session.upload_progress.enabled=0.