Pe scurt: La securitatea PHP unserialize se aplică o regulă simplă: nu procesați niciodată cu unserialize() date care vin din exterior. Folosiți în schimb JSON. Acolo unde unserialize() trebuie să rămână din cod vechi, limitați-l cu allowed_classes => false și verificați autenticitatea datelor cu o semnătură.
Ce poate merge prost cu unserialize()
PHP poate transforma array-uri și obiecte într-un șir de caractere cu serialize() și le poate restabili cu unserialize(). Este comod pentru a depune date în cookieuri, cache-uri sau câmpuri de bază de date. Problema: unserialize() creează astfel și obiecte de clase arbitrare, care pot fi încărcate în aplicație.
La crearea și cel târziu la distrugerea unui asemenea obiect, PHP apelează automat anumite metode, de exemplu __wakeup(), __unserialize() sau __destruct(). Dacă clasele din proiect sau din biblioteci conțin asemenea metode cu acțiuni asupra fișierelor, bazei de date sau sistemului, un atacator le poate abate de la scop prin date manipulate. Specialiștii vorbesc de PHP Object Injection. În funcție de clasele existente, efectul merge de la ștergerea unor fișiere individuale până la executarea de cod malițios.
Rezumat pentru netehnicieni: un singur apel nechibzuit, într-un loc greșit, poate lăsa un atacator să pătrundă adânc în aplicație. Această slăbiciune se numără de aceea de ani de zile printre temele OWASP Top 10.
Alternativa sigură: JSON în loc de unserialize
Pentru aproape toate cazurile în care datele vin din exterior sau părăsesc serverul, JSON este alegerea corectă. json_decode() creează doar array-uri, numere, șiruri de caractere și valori booleene, nu obiecte ale unor clase proprii.
<?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.');
}
// Preluați doar câmpurile cunoscute, cu tipurile așteptate
return [
'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
'seiten' => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
];
}
Important este a doua parte: și datele JSON sunt date de intrare. Preluați doar câmpurile așteptate și verificați tipurile și intervalele de valori înainte de a lucra cu ele.
Când unserialize este inevitabil: allowed_classes și semnătură
În aplicațiile mai vechi, datele serializate se află adesea în baze de date sau în cache-uri care nu pot fi trecute imediat la altceva. Atunci limitați riscul în doi pași. Mai întâi, împiedicați cu allowed_classes crearea de obiecte. Apoi, asigurați cu o semnătură HMAC că datele provin într-adevăr de la aplicația dumneavoastră și nu au fost modificate pe drum.
<?php
declare(strict_types=1);
const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // niciodată în depozitul de cod
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;
}
Dacă sunt totuși necesare anumite clase, enumerați-le individual în allowed_classes, în loc să le permiteți pe toate. Cheia aparține configurației serverului sau unui depozit de secrete și trebuie reînnoită după un incident.
Trecerea treptată a datelor existente la JSON
La aplicațiile crescute în timp, valorile serializate se află adesea ani de zile în baza de date. O trecere într-un singur pas este atunci riscantă. S-a dovedit utilă o tranziție: aplicația citește ambele formate, dar scrie doar JSON. La citirea unei valori vechi, aceasta este despachetată o dată cu allowed_classes => false și rescrisă imediat ca JSON. După un timp, majoritatea intrărilor sunt convertite, iar un script unic se ocupă de restul.
Înregistrați în timpul tranziției cât de des se mai citește formatul vechi, precum și când semnăturile nu se potrivesc. Asemenea eșecuri pot indica încercări de manipulare și aparțin monitorizării. Abia când nu mai apare niciun format vechi, eliminați complet ramura unserialize() din cod.
Deserializare ascunsă: sesiuni, cache-uri și căi de fișiere
Nu orice deserializare se vede în codul propriu. Verificați și aceste locuri:
- Sesiuni: PHP serializează automat datele de sesiune. Atâta timp cât atacatorii nu pot scrie fișierele de sesiune, este necritic. Căi de sesiune separate pentru fiecare site descrie ghidul Cum securizați sesiunile în PHP.
- Serializatorul de sesiuni: Anunțul despre CVE-2026-115560 privește o configurare neobișnuită, în care
serialize_handlereste comutat în timpul rulării prinini_set(), iar session upload progress este activ. Măsura conform php.net:session.upload_progress.enabled=0în php.ini sau în pool-ul FPM, apoi repornirea workerilor. - Cache-uri și cozi de așteptare: Redis, Memcached sau tabelele de bază de date cu valori serializate sunt la fel de sigure ca accesul la ele. Ele nu aparțin deschis în rețea.
- Funcții de fișiere cu căi din date de intrare: Nu transmiteți niciodată funcțiilor de fișiere căi din cerere. Wrappere de stream precum
phar://pot altfel declanșa procesări neașteptate. O listă de permisiuni pentru numele de fișiere protejează aici mai fiabil decât orice filtrare. - Dependențe: Bibliotecile cu slăbiciuni cunoscute lărgesc posibilitățile unui atacator. Mențineți-le la zi, așa cum descrie ghidul composer audit: verificarea vulnerabilităților din dependențe.
Găsirea locurilor riscante din cod
O primă inventariere reușește cu o căutare simplă. Ea nu modifică nimic în sistem și arată toate apelurile din afara directorului vendor:
grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
| grep -v '/vendor/'
Evaluați fiecare rezultat cu trei întrebări: de unde vin datele? Le poate modifica un utilizator? Este setat allowed_classes? Rezultatele cu date de intrare din cookieuri, $_GET, $_POST, $_REQUEST sau interfețe externe au prioritate. Înlocuiți-le cu JSON și testați trecerea înainte de a o lansa.
Un alt reper se află în browser. Datele PHP serializate au o structură tipică, cu identificatori de tip și indicații de lungime. Dacă găsiți asemenea valori în cookieuri, în câmpuri ascunse de formular sau în parametri ai propriului site, aplicația a dat vizitatorului date serializate și probabil le așteaptă înapoi. Tocmai aceste locuri trebuie verificate.
Pentru conducere este suficientă o întrebare simplă către dezvoltatori sau agenție: „Procesează aplicația noastră undeva date serializate care vin din exterior și, dacă da, cum sunt protejate?” Un răspuns clar, cu locurile găsite, este un semn bun. Un răspuns evaziv este un motiv să lăsați aplicația să fie verificată.
Cum ajută PHP SOS concret?
- Căutăm în codul dumneavoastră deserializare cu date străine și evaluăm fiecare constatare după accesibilitate și impact.
- Propunem treceri concrete la JSON sau apeluri securizate și ne însoțim dezvoltatorii la implementare.
- Verificăm dependențele pentru slăbiciuni cunoscute și documentăm starea.
- La cerere, lăsăm aplicația să fie verificată independent în cadrul unui audit de securitate și test de penetrare.
Întrebări frecvente
Este unserialize() în PHP nesigur în mod fundamental?
Nu pentru date pe care le generează doar propria aplicație și pe care nu le poate modifica nimeni. Devine periculos de îndată ce utilizatorii sau terții pot influența datele de intrare, de exemplu prin cookieuri, câmpuri de formular sau interfețe externe.
Este allowed_classes => false suficient ca protecție?
Împiedică crearea de obiecte arbitrare și închide astfel calea de atac cea mai importantă. Pentru datele de intrare din exterior, JSON rămâne totuși alegerea mai bună, deoarece parserul este mai simplu și nu reconstruiește obiecte.
Ajută actualizarea bibliotecilor împotriva atacurilor de deserializare?
În parte. Atacurile folosesc adesea clase din dependențe care execută acțiuni la distrugerea sau la trezirea unui obiect. Pachetele actuale închid lanțurile cunoscute, dar cauza reală este apelul unserialize cu date străine.
Are PHP2Shell legătură cu deserializarea?
Cele două vulnerabilități principale se află în procesarea cererilor de către PHP. Anunțul despre CVE-2026-115560 privește, în schimb, o configurare neobișnuită a serializării sesiunilor. Măsura provizorie este acolo session.upload_progress.enabled=0.