# Istuntojen suojaaminen PHP:ssä: PHP-istuntojen tietoturva käytännössä

> PHP-istuntojen tietoturva vaihe vaiheelta: turvalliset php.ini-arvot, uusi istuntotunnus kirjautumisen jälkeen, evästeiden liput ja PHP2Shellin latauksia koskeva ohje.

Lähde: https://phpsos.de/fi/ratgeber/session-sicherheit-php/  
Tilanne: 25.10.2026  
Palveluntarjoaja: PHP SOS, PHP-hätäapu ja palvelimen tietoturva. Pikaapu WhatsAppilla: https://wa.me/491736494914?text=Hei%20PHP%20SOS%2C%20tarvitsemme%20apua%20PHP-palvelimemme%20kanssa.%20Yritys%3A%20%E2%80%A6%20Verkkosivusto%3A%20%E2%80%A6

**Lyhyesti:** PHP-istuntojen tietoturvassa on neljä ratkaisevaa asiaa: turvalliset evästeasetukset, uusi istuntotunnus jokaisen kirjautumisen jälkeen, erilliset ja suojatut tallennuspaikat sekä lyhyet voimassaoloajat. PHP2Shellin vuoksi `session.upload_progress.enabled=0` pitäisi lisäksi asettaa palvelinpuolella, jos tälle toiminnolle ei ole syytä.

## Miksi istunnot ovat houkutteleva kohde

Istunto muistaa, että käyttäjä on kirjautunut. Teknisesti selain saa tätä varten satunnaisen istuntotunnuksen evästeessä, ja palvelin tallentaa siihen kuuluvat tiedot. Joka tuntee tämän tunnuksen, on sovellukselle sama käyttäjä, ilman salasanaa ja ilman toista tekijää.

Ylläpitäjille tämä tarkoittaa: heikko istuntohallinta voi kumota kirjautumissuojan, vaikka salasanat ja kaksivaiheinen tunnistautuminen olisi toteutettu siististi. Erityisen kriittistä se on ylläpitoalueilla, asiakastileillä ja portaaleissa, joissa on henkilötietoja.

Tyypillisiä riskejä ovat istuntotunnuksen siepkaus (istuntokaappaus), tunnetun tunnuksen syöttäminen ennen kirjautumista (Session Fixation) ja liian pitkään voimassa olevat istunnot jaetuilla koneilla.

## Turvalliset perusasetukset php.ini-tiedostossa

Tärkeimmät arvot voi asettaa keskitetysti `php.ini`-tiedostossa tai verkkosivustokohtaisesti FPM-poolissa. Poolissa käyttäkää `php_admin_value`- tai `php_admin_flag`-asetusta, jotta sovellus ei voi ylikirjoittaa arvoja.

```ini
; Istuntojen perusvahvistus
session.use_strict_mode   = 1
session.use_only_cookies  = 1
session.use_trans_sid     = 0
session.cookie_httponly   = 1
session.cookie_secure     = 1
session.cookie_samesite   = "Lax"
session.cookie_lifetime   = 0
session.gc_maxlifetime    = 1800
session.name              = "APPSESSID"

; php.netin PHP2Shell-ohje, käynnistä sen jälkeen kaikki worker-prosessit uudelleen
session.upload_progress.enabled = 0
```

`use_strict_mode` huolehtii siitä, ettei PHP ota käyttöön selaimen antamia tuntemattomia tunnuksia. `cookie_httponly` estää JavaScriptiä lukemasta tunnusta, mikä rajoittaa Cross-Site Scriptingin seurauksia. `cookie_secure` lähettää evästeen vain HTTPS:n kautta. `SameSite=Lax` vaikeuttaa sivustojen välisiä pyyntöjä voimassa olevalla istunnolla; pelkille ylläpitoalueille voi sopia `Strict`.

Voimassaoloaika 1800 sekuntia on esimerkkiarvo. Määrittäkää se suojaustarpeen mukaan: lyhyempi ylläpidolle ja asiakastiedoille, pidempi vain siellä, missä se on toiminnallisesti tarpeen.

Huomatkaa, että `gc_maxlifetime` määrittää vain, mistä lähtien vanhat istuntotiedot saa poistaa. Joissakin jakeluissa istuntotiedostot siivoaa PHP:n sijaan säännöllinen järjestelmätehtävä. Tarkistakaa silloin, mitä arvoa tämä tehtävä käyttää, jotta asetuksenne todella tehoaa. Älkää myöskään luottako uloskirjautumisessa käyttämättömyyden jälkeen pelkkään siivoustoimintoon, vaan tarkistakaa viimeisen toiminnon ajankohta sovelluksessa itse, kuten seuraavassa esimerkissä näytetään.

## Istuntotunnuksen uusiminen kirjautumisen jälkeen

Koodin tärkein sääntö on luoda istuntotunnus uudelleen jokaisen oikeuksien muutoksen yhteydessä, siis kirjautumisessa, siirryttäessä ylläpitotilaan ja uloskirjautumisessa. Seuraava esimerkki näyttää siistin perusmallin `password_verify()`-funktiolla:

```php
<?php
declare(strict_types=1);

session_start();

function anmelden(PDO $db, string $email, string $passwort): bool
{
    $stmt = $db->prepare('SELECT id, passwort_hash FROM benutzer WHERE email = :email');
    $stmt->execute(['email' => $email]);
    $nutzer = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$nutzer || !password_verify($passwort, $nutzer['passwort_hash'])) {
        return false; // sama ilmoitus väärälle osoitteelle ja väärälle salasanalle
    }

    session_regenerate_id(true);           // uusi tunnus, vanha hylätään
    $_SESSION['benutzer_id']   = (int) $nutzer['id'];
    $_SESSION['letzte_aktion'] = time();
    return true;
}

function pruefeLeerlauf(int $sekunden = 900): void
{
    if (isset($_SESSION['letzte_aktion']) && time() - $_SESSION['letzte_aktion'] > $sekunden) {
        abmelden();
        return;
    }
    $_SESSION['letzte_aktion'] = time();
}

function abmelden(): void
{
    $_SESSION = [];
    $p = session_get_cookie_params();
    setcookie(session_name(), '', [
        'expires'  => time() - 3600,
        'path'     => $p['path'],
        'domain'   => $p['domain'],
        'secure'   => $p['secure'],
        'httponly' => $p['httponly'],
        'samesite' => $p['samesite'],
    ]);
    session_destroy();
}
```

Suojatkaa lisäksi lomakkeet, jotka muuttavat jotain, CSRF-tunnisteella. Se luodaan funktiolla `bin2hex(random_bytes(32))`, tallennetaan istuntoon ja verrataan lähetettäessä funktiolla `hash_equals()`.

## Tallennuspaikat ja asiakaserotus

Oletuksena PHP tallentaa istunnot tiedostoina yhteiseen hakemistoon. Palvelimella, jolla on useita verkkosivustoja, se voi tarkoittaa, että vaarantunut verkkosivusto voi lukea toisen sivuston istuntoja. Siksi jokaiselle verkkosivustolle kuuluu oma pooli, oma käyttäjä ja oma istuntopolku.

```ini
; kunkin verkkosivuston FPM-poolissa
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
```

Hakemiston pitäisi kuulua pool-käyttäjälle ja sillä pitäisi olla oikeudet `0700`. Miten erotatte verkkosivustot toisistaan lisäksi, kuvataan oppaassa [open_basedir ja sivustokohtainen eristys](https://phpsos.de/fi/ratgeber/open-basedir/). Jos istunnot tallennetaan Redisiin tai tietokantaan, pätevät samat periaatteet: oma pääsy kutakin sovellusta kohden, ei avointa verkkoyhteyttä.

## PHP-istuntojen tietoturva ja PHP2Shell

PHP2Shellin haavoittuvuudet CVE-2026-109618 ja CVE-2026-109619 ovat PHP:ssä itsessään. Yhtenä väliaikaisena toimena php.net mainitsee asettaa `session.upload_progress.enabled=0` php.ini-tiedostossa, FPM-poolissa tai palvelimen asetuksissa ja käynnistää sen jälkeen kaikki worker-prosessit uudelleen. Nimenomaan ei `ini_set()`-komennolla eikä `.user.ini`-tiedostolla.

Tiedote [CVE-2026-115560:stä](https://phpsos.de/fi/php2shell/cve-2026-115560/) tuli 23.10.2026. Mahdollinen koodin suoritus on siinä mahdollista vain epätavallisella istuntojen serialisoinnin, ennen `session_start()`-kutsua `ini_set()`-komennolla tehdyn vaihdon ja aktiivisen Upload Progressin yhdistelmällä. Keino on sama asetus seuraavaan tavanomaiseen virhekorjausjulkaisuun asti.

> **Tärkeää:**
> Tämä asetus on väliaikainen toimi eikä täydellinen suoja. Ratkaisevaa on yhä päivitys korjattuun PHP-versioon ja sitä seuraava [kaikkien PHP-worker-prosessien uudelleenkäynnistys](https://phpsos.de/fi/php2shell/php-fpm-neustart/).

Tarkistakaa muutoksen jälkeen, mikä arvo todella on voimassa. Komentorivi näyttää usein eri asetukset kuin verkkopalvelin; luotettava keino on suojattu, sen jälkeen poistettu tarkistussivu tai itse pool-asetukset.

## Tyypilliset virheet istunnoissa PHP-sovelluksissa

Monet heikkoudet eivät synny puuttuvista asetuksista vaan siitä, miten sovellukset käyttävät istuntoja. Nämä mallit kannattaa selvittää kohdennetusti omasta koodistanne ja palveluntarjoajilta:

- **Liikaa tietoa istunnossa:** Salasanat, täydelliset maksutiedot tai suuret tietueet eivät kuulu istuntoon. Tallentakaa vain se, mikä on tarpeen tunnistamiseen, esimerkiksi käyttäjätunnus ja roolit.
- **Oikeudet vain istunnosta:** Se, onko joku ylläpitäjä, pitäisi kriittisissä toimissa tarkistaa tietokannasta. Muuten poistetut oikeudet pysyvät voimassa istunnon päättymiseen asti.
- **Istuntotunnus osoitteessa:** URL-osoitteiden tunnukset päätyvät lokeihin, selainhistoriaan ja Referer-tietoihin. `use_only_cookies` ja `use_trans_sid = 0` estävät tämän.
- **Istunnolla ei ole loppua:** Jos käyttämättömyyden aikakatkaisua ei ole, ylläpitäjä pysyy jaetulla koneella kirjautuneena rajattomasti.
- **HTTPS tunnistetaan väärin välityspalvelimen takana:** Jos salaus päättyy kuormantasaajaan, sovelluksen on tiedettävä se. Muuten se voi asettaa evästeet ilman `Secure`-lippua.

Tietoturvapoikkeaman jälkeen kuuluu lisäksi hylätä kaikki olemassa olevat istunnot. Helpoin tapa on poistaa istuntohakemiston sisältö tai vaihtaa istunnon nimi. Kaikkien käyttäjien on silloin kirjauduttava uudelleen, ja varastetut istunnot ovat arvottomia.

## Miten PHP SOS auttaa konkreettisesti?

- Tarkistamme istunta-asetukset, evästeiden liput ja tallennuspaikat palvelimen kaikilla verkkosivustoilla ja dokumentoimme todella voimassa olevat arvot.
- Otamme PHP2Shellin väliaikaiset toimet käyttöön pooleittain, käynnistämme worker-prosessit hallitusti uudelleen ja osoitamme tuloksen.
- Erotamme verkkosivustot omilla pooleilla ja istuntopoluilla osana [palvelimen koventamista](https://phpsos.de/fi/leistungen/server-haertung/).
- Omille kehitystöille tarkistamme kirjautumisen, uloskirjautumisen ja istunnon vaihdon koodissa ja suosittelemme konkreettisia muutoksia.


## Usein kysytyt kysymykset

**Mikä on istuntokaappaus?**  
Istuntokaappauksessa hyökkääjä ottaa kirjautuneen käyttäjän istunnon haltuunsa saamalla tämän istuntotunnuksen. Tunnuksella hän on sovellukselle kyseinen käyttäjä tuntematta salasanaa. Turvalliset evästeiden liput ja lyhyet voimassaoloajat vaikeuttavat tätä selvästi.

**Miksi istuntotunnus on luotava uudelleen kirjautumisen jälkeen?**  
Jos tunnus pysyy samana ennen kirjautumista ja sen jälkeen, aiemmin tunnettu tai syötetty tunnus voisi olla voimassa kirjautumisen jälkeenkin. session_regenerate_id(true) luo uuden tunnuksen ja hylkää vanhan.

**Voinko poistaa session.upload_progress.enabled-asetuksen käytöstä ini_set()-komennolla?**  
Ei, se ei php.netin mukaan riitä. Asetus kuuluu php.ini-tiedostoon, FPM-pooliin tai palvelimen asetuksiin, ei ini_set()-komentoon eikä .user.ini-tiedostoon. Sen jälkeen kaikki PHP-worker-prosessit on käynnistettävä uudelleen.

**Minne istuntotiedostot pitäisi tallentaa?**  
Web-juuren ulkopuoliseen hakemistoon, jota saa lukea vain kyseisen PHP-poolin käyttäjä. Palvelimilla, joilla on useita verkkosivustoja, jokaisella sivustolla pitäisi olla oma istuntohakemisto.

## Lähteet

- [OWASP Cheat Sheet Series (Session Management)](https://cheatsheetseries.owasp.org/)
- [php.net: PHP2Shellin tietoturvatiedotteet](https://www.php.net/)
- [PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
