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ä →

Turvaton deserialisointi PHP:ssä: unserialize ja tietoturva

unserialize() vierailla tiedoilla voi pahimmillaan johtaa koodin suoritukseen. Näin korvaatte sen JSON:lla, rajaatte sen siellä, missä sen on pakko jäädä, ja löydätte riskialttiit kohdat koodista.

Tilanne Lukuaika 4 minOppaat

Lyhyesti: PHP unserialize -tietoturvassa pätee yksinkertainen sääntö: älkää koskaan käsitelkö ulkopuolelta tulevia tietoja unserialize()-funktiolla. Käyttäkää sen sijaan JSON:ia. Missä unserialize() on vanhasta koodista pakko säilyttää, rajatkaa se asetuksella allowed_classes => false ja tarkistakaa tietojen aitous allekirjoituksella.

Mikä unserialize()-funktiossa voi mennä pieleen

PHP voi muuntaa taulukot ja objektit serialize()-funktiolla merkkijonoksi ja palauttaa ne unserialize()-funktiolla. Se on kätevää, kun tietoja tallennetaan evästeisiin, välimuisteihin tai tietokantakenttiin. Ongelma: unserialize() luo samalla myös mielivaltaisten luokkien objekteja, jotka voidaan ladata sovelluksessa.

Tällaisen objektin luomisen yhteydessä ja viimeistään sen tuhoamisen yhteydessä PHP kutsuu automaattisesti tiettyjä metodeja, kuten __wakeup(), __unserialize() tai __destruct(). Jos projektin tai kirjastojen luokissa on tällaisia metodeja, jotka tekevät tiedosto-, tietokanta- tai järjestelmätoimintoja, hyökkääjä voi käyttää niitä väärin manipuloiduilla tiedoilla. Asiantuntijat puhuvat PHP Object Injectionista. Olemassa olevista luokista riippuen vaikutus ulottuu yksittäisten tiedostojen poistamisesta haittakoodin suorittamiseen.

Ei-teknisille tiivistettynä: yksi harkitsematon kutsu väärässä paikassa voi päästää hyökkääjän syvälle sovellukseen. Siksi tämä heikkous on vuosia kuulunut OWASP Top 10 -aiheisiin.

Turvallinen vaihtoehto: JSON unserializen sijaan

Lähes kaikissa tapauksissa, joissa tiedot tulevat ulkopuolelta tai poistuvat palvelimelta, JSON on oikea valinta. json_decode() luo vain taulukoita, lukuja, merkkijonoja ja totuusarvoja, ei omien luokkien objekteja.

<?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.');
    }

    // Ota käyttöön vain tunnetut kentät odotetuilla tyypeillä
    return [
        'sprache' => in_array($daten['sprache'] ?? '', ['de', 'en'], true) ? $daten['sprache'] : 'de',
        'seiten'  => max(1, min(100, (int) ($daten['seiten'] ?? 20))),
    ];
}

Tärkeä on toinen osa: myös JSON-tiedot ovat syötteitä. Ottakaa käyttöön vain odotetut kentät ja tarkistakaa tyypit ja arvoalueet, ennen kuin käsittelette niitä.

Kun unserialize on väistämätön: allowed_classes ja allekirjoitus

Vanhemmissa sovelluksissa serialisoituja tietoja on usein tietokannoissa tai välimuisteissa, joita ei voi muuttaa heti. Silloin rajaatte riskiä kahdessa vaiheessa. Ensiksi estätte allowed_classes-asetuksella objektien luomisen. Toiseksi varmistatte HMAC-allekirjoituksella, että tiedot todella tulevat sovelluksestanne eikä niitä ole muutettu matkalla.

<?php
declare(strict_types=1);

const SCHLUESSEL = 'aus-sicherer-konfiguration-laden'; // ei koskaan koodirepositoriossa

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;
}

Jos tiettyjä luokkia kuitenkin tarvitaan, nimetkää ne yksitellen allowed_classes-asetuksessa sen sijaan, että sallisitte kaikki. Avain kuuluu palvelimen asetuksiin tai salaisuuksien tallennukseen, ja se on uusittava poikkeaman jälkeen.

Olemassa olevien tietojen vaiheittainen siirto JSON:iin

Vanhoissa sovelluksissa serialisoituja arvoja on usein tietokannassa vuosien ajalta. Siirto yhdessä vaiheessa on silloin riskialtista. Siirtymävaihe on osoittautunut toimivaksi: sovellus lukee molempia muotoja mutta kirjoittaa enää vain JSON:ia. Vanhaa arvoa luettaessa se puretaan kerran asetuksella allowed_classes => false ja kirjoitetaan heti takaisin JSON:na. Jonkin ajan kuluttua suurin osa merkinnöistä on siirretty, ja kertaluonteinen komentosarja hoitaa loput.

Kirjatkaa siirtymän aikana, kuinka usein vanhaa muotoa vielä luetaan, ja myös silloin, kun allekirjoitukset eivät täsmää. Tällaiset epäonnistumiset voivat viitata manipulointiyrityksiin ja kuuluvat valvontaan. Vasta kun vanhaa muotoa ei enää esiinny, poistakaa unserialize()-haara koodista kokonaan.

Piilotettu deserialisointi: istunnot, välimuistit ja tiedostopolut

Kaikki deserialisointi ei näy omassa koodissa. Tarkistakaa myös nämä kohdat:

  • Istunnot: PHP serialisoi istuntotiedot automaattisesti. Niin kauan kuin hyökkääjät eivät voi kirjoittaa istuntotiedostoja, se on vaaratonta. Verkkosivustokohtaiset istuntopolut kuvataan oppaassa Istuntojen suojaaminen PHP:ssä.
  • Istuntojen serialisoija: Tiedote CVE-2026-115560:stä koskee epätavallista asetusta, jossa serialize_handler vaihdetaan ajon aikana ini_set()-komennolla ja Session Upload Progress on aktiivinen. php.netin mukainen keino: session.upload_progress.enabled=0 php.ini-tiedostossa tai FPM-poolissa, sen jälkeen worker-prosessien uudelleenkäynnistys.
  • Välimuistit ja jonot: Redis, Memcached tai tietokantataulut, joissa on serialisoituja arvoja, ovat yhtä turvallisia kuin pääsy niihin. Niitä ei pidä jättää avoimiksi verkkoon.
  • Tiedostofunktiot syötteistä tulevilla poluilla: Älkää koskaan antako tiedostofunktioille polkuja pyynnöstä. Stream-wrapperit kuten phar:// voivat muuten käynnistää odottamatonta käsittelyä. Tiedostonimien sallittujen luettelo suojaa tässä luotettavammin kuin mikään suodatus.
  • Riippuvuudet: Kirjastot, joissa on tunnettuja heikkouksia, laajentavat hyökkääjän mahdollisuuksia. Pitäkää ne ajan tasalla, kuten oppaassa Composer-riippuvuuksien haavoittuvuuksien tarkistus kuvataan.

Riskialttiiden kohtien löytäminen koodista

Ensimmäinen kartoitus onnistuu yksinkertaisella haulla. Se ei muuta järjestelmää mitenkään ja näyttää kaikki kutsut vendor-hakemiston ulkopuolella:

grep -rn --include='*.php' 'unserialize(' /var/www/projekt \
  | grep -v '/vendor/'

Arvioikaa jokainen osuma kolmella kysymyksellä: mistä tiedot tulevat? Voiko käyttäjä muuttaa niitä? Onko allowed_classes asetettu? Osumat, joissa syötteet tulevat evästeistä, muuttujista $_GET, $_POST, $_REQUEST tai ulkoisista rajapinnoista, ovat etusijalla. Korvatkaa ne JSON:lla ja testatkaa siirtymä, ennen kuin otatte sen käyttöön.

Toinen vihje löytyy selaimesta. Serialisoiduilla PHP-tiedoilla on tyypillinen rakenne, jossa on tyyppitunnisteet ja pituustiedot. Jos löydätte tällaisia arvoja evästeistä, piilotetuista lomakekentistä tai omien verkkosivustojenne parametreista, sovellus on antanut serialisoituja tietoja kävijälle ja odottaa niitä todennäköisesti takaisin. Juuri nämä kohdat on tarkistettava.

Johdolle riittää yksinkertainen kysymys kehittäjille tai toimistolle: ”Käsitteleekö sovelluksemme jossain ulkopuolelta tulevaa serialisoitua dataa, ja jos käsittelee, miten se on suojattu?” Selkeä vastaus löytökohtineen on hyvä merkki. Kiertelevä vastaus on syy teettää sovelluksen tarkistus.

Miten PHP SOS auttaa konkreettisesti?

  • Käymme koodinne läpi deserialisoinnin osalta vierailla tiedoilla ja arvioimme jokaisen löydön tavoitettavuuden ja vaikutuksen mukaan.
  • Ehdotamme konkreettisia siirtymiä JSON:iin tai suojattuihin kutsuihin ja tuemme kehittäjiänne toteutuksessa.
  • Tarkistamme riippuvuudet tunnettujen heikkouksien varalta ja dokumentoimme tilan.
  • Halutessanne annamme sovelluksen tarkistuttaa riippumattomasti osana tietoturva-auditointia ja penetraatiotestiä.

Usein kysytyt kysymykset

Onko unserialize() PHP:ssä periaatteessa turvaton?

Ei tiedoille, jotka vain oma sovelluksenne tuottaa ja joita kukaan ei voi muuttaa. Vaaralliseksi se muuttuu heti, kun käyttäjät tai kolmannet osapuolet voivat vaikuttaa syötteeseen, esimerkiksi evästeiden, lomakekenttien tai ulkoisten rajapintojen kautta.

Riittääkö allowed_classes => false suojaksi?

Se estää mielivaltaisten objektien luomisen ja sulkee siten tärkeimmän hyökkäysreitin. Ulkopuolelta tuleville syötteille JSON on silti parempi valinta, koska jäsennin on yksinkertaisempi eikä se rekonstruoi objekteja.

Auttaako kirjastojen päivitys deserialisointihyökkäyksiä vastaan?

Osittain. Hyökkäykset käyttävät usein riippuvuuksien luokkia, jotka suorittavat toimintoja objektia tuhottaessa tai herätettäessä. Ajantasaiset paketit sulkevat tunnetut ketjut, mutta varsinainen syy on unserialize-kutsu vierailla tiedoilla.

Onko PHP2Shellillä jotain tekemistä deserialisoinnin kanssa?

Kaksi pääaukkoa ovat PHP:n pyyntöjen käsittelyssä. CVE-2026-115560:tä koskeva tiedote sen sijaan liittyy istuntojen serialisoinnin epätavalliseen asetukseen. Keino on siinä session.upload_progress.enabled=0.

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