Ukratko: Kod sigurnosti PHP unserialize vrijedi jednostavno pravilo: podatke koji dolaze izvana nikad ne obrađivati s unserialize(). Umjesto toga koristite JSON. Gdje unserialize() iz starog koda mora ostati, ograničite ga s allowed_classes => false i autentičnost podataka provjeravajte potpisom.
Što kod unserialize() može poći po zlu
PHP može nizove i objekte pretvoriti u znakovni niz s serialize() i vratiti s unserialize(). To je zgodno za pohranu podataka u kolačiće, cacheve ili polja baze podataka. Problem: unserialize() pritom stvara i objekte proizvoljnih klasa koje se u aplikaciji mogu učitati.
Pri stvaranju i najkasnije pri uništavanju takvog objekta PHP automatski poziva određene metode, primjerice __wakeup(), __unserialize() ili __destruct(). Ako klase u projektu ili u bibliotekama imaju takve metode s radnjama nad datotekama, bazom podataka ili sustavom, napadač ih može zloupotrijebiti putem manipuliranih podataka. Stručnjaci govore o PHP Object Injectionu. Ovisno o postojećim klasama, učinak seže od brisanja pojedinih datoteka do izvršavanja zlonamjernog koda.
Za netehničke osobe ukratko: jedan jedini nepromišljen poziv na pogrešnom mjestu može napadača pustiti duboko u aplikaciju. Ta slabost zato godinama spada među teme OWASP Top 10.
Sigurna alternativa: JSON umjesto unserialize
Za gotovo sve slučajeve u kojima podaci dolaze izvana ili napuštaju poslužitelj, JSON je pravi izbor. json_decode() stvara samo nizove, brojeve, znakovne nizove i logičke vrijednosti, a ne objekte vlastitih klasa.
<?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.');
}
// Preuzeti samo poznata polja s očekivanim tipovima
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Važan je drugi dio: i JSON podaci su unosi. Preuzmite samo očekivana polja te provjerite tipove i raspone vrijednosti prije nego što s njima radite.
Kad je unserialize neizbježan: allowed_classes i potpis
U starijim aplikacijama serijalizirani podaci često su u bazama podataka ili cacheovima koji se ne mogu odmah prebaciti. Tada rizik ograničite u dva koraka. Prvo, s allowed_classes sprječavate stvaranje objekata. Drugo, HMAC potpisom osiguravate da podaci zaista potječu iz Vaše aplikacije i da nisu promijenjeni na putu.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // nikad u repozitoriju koda
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;
}
Ako se određene klase ipak trebaju, navedite ih pojedinačno u allowed_classes umjesto da dopustite sve. Ključ pripada u konfiguraciju poslužitelja ili pohranu tajni te ga nakon incidenta treba obnoviti.
Postupni prelazak postojećih podataka na JSON
Kod razvijenih aplikacija serijalizirane vrijednosti često godinama stoje u bazi podataka. Prelazak u jednom koraku tada je rizičan. Pokazao se dobrim postupni prijelaz: aplikacija čita oba formata, ali piše samo JSON. Pri čitanju stare vrijednosti jednokratno se raspakira s allowed_classes => false i odmah zapiše natrag kao JSON. Nakon nekog vremena većina je zapisa prebačena, a jednokratna skripta obavlja ostatak.
Tijekom prijelaza bilježite koliko se često još čita stari format, kao i kad potpisi ne odgovaraju. Takvi neuspjesi mogu upućivati na pokušaje manipulacije i pripadaju u nadzor. Tek kad se stari format više ne pojavljuje, granu unserialize() potpuno uklonite iz koda.
Skrivena deserijalizacija: sesije, cacheovi i putanje datoteka
Nije svaka deserijalizacija vidljiva u vlastitom kodu. Provjerite i ova mjesta:
- Sesije: PHP automatski serijalizira podatke sesije. Sve dok napadači ne mogu pisati u datoteke sesija, to je nekritično. Odvojene putanje sesija po web-stranici opisuje vodič Osiguravanje sesija u PHP-u.
- Serijalizatori sesija: Obavijest o CVE-2026-115560 tiče se neuobičajene konfiguracije u kojoj se
serialize_handlertijekom rada prebacuje putemini_set()i Session Upload Progress je aktivan. Zaobilazno rješenje prema php.netu:session.upload_progress.enabled=0u php.ini ili FPM poolu, potom ponovno pokretanje workera. - Cacheovi i redovi čekanja: Redis, Memcached ili tablice baze podataka sa serijaliziranim vrijednostima sigurni su samo koliko i pristup njima. Ne pripadaju otvoreno u mrežu.
- Funkcije za datoteke s putanjama iz unosa: Funkcijama za datoteke nikad ne predajte putanje iz zahtjeva. Stream wrapperi poput
phar://inače mogu izazvati neočekivanu obradu. Popis dopuštenih naziva datoteka tu štiti pouzdanije od bilo kakvog filtriranja. - Ovisnosti: Biblioteke s poznatim slabostima proširuju mogućnosti napadača. Držite ih ažurnima, kako opisuje vodič Provjera Composer ovisnosti na ranjivosti.
Pronalaženje rizičnih mjesta u kodu
Prvi popis uspijeva jednostavnom pretragom. Ona ništa ne mijenja u sustavu i prikazuje sve pozive izvan direktorija vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Svaki pogodak ocijenite s tri pitanja: Odakle podaci dolaze? Može li ih korisnik mijenjati? Je li allowed_classes postavljen? Pogoci s unosima iz kolačića, $_GET, $_POST, $_REQUEST ili vanjskih sučelja imaju prednost. Zamijenite ih JSON-om i testirajte prelazak prije puštanja u rad.
Dodatno uporište nalazi se u pregledniku. Serijalizirani PHP podaci imaju tipičnu građu s oznakama tipova i duljinama. Ako takve vrijednosti pronađete u kolačićima, skrivenim poljima obrazaca ili parametrima vlastite web-stranice, aplikacija je serijalizirane podatke predala posjetitelju i vjerojatno ih očekuje natrag. Upravo ta mjesta treba provjeriti.
Za upravu je dovoljno jednostavno pitanje programerima ili agenciji: „Obrađuje li naša aplikacija negdje serijalizirane podatke koji dolaze izvana i, ako da, kako su zaštićeni?“ Jasan odgovor s mjestima pronalaska dobar je znak. Izbjegavajući odgovor razlog je da se aplikacija dade provjeriti.
Kako PHP SOS konkretno pomaže?
- Pretražujemo Vaš kod na deserijalizaciju s tuđim podacima i ocjenjujemo svaki nalaz prema dostupnosti i učinku.
- Predlažemo konkretne prelaske na JSON ili osigurane pozive i pratimo Vaše programere pri provedbi.
- Provjeravamo ovisnosti na poznate slabosti i dokumentiramo stanje.
- Na željeno aplikaciju neovisno provjeravamo u okviru sigurnosnog audita i penetracijskog testa.
Česta pitanja
Je li unserialize() u PHP-u u osnovi nesiguran?
Ne za podatke koje stvara samo Vaša vlastita aplikacija i koje nitko ne može mijenjati. Opasno postaje čim korisnici ili treće strane mogu utjecati na unos, primjerice putem kolačića, polja obrazaca ili vanjskih sučelja.
Je li allowed_classes => false dovoljna zaštita?
Sprječava stvaranje proizvoljnih objekata i time zatvara najvažniji put napada. Za unose izvana JSON je ipak bolji izbor jer je parser jednostavniji i ne rekonstruira objekte.
Pomaže li ažuriranje biblioteka protiv napada deserijalizacijom?
Djelomično. Napadi često koriste klase iz ovisnosti koje pri uništavanju ili buđenju objekta izvršavaju radnje. Aktualni paketi zatvaraju poznate lance, ali stvarni je uzrok poziv unserialize s tuđim podacima.
Ima li PHP2Shell veze s deserijalizacijom?
Dvije glavne ranjivosti su u obradi zahtjeva u PHP-u. Obavijest o CVE-2026-115560 pak se tiče neuobičajene konfiguracije serijalizacije sesija. Zaobilazno rješenje tamo je session.upload_progress.enabled=0.