# Sessies in PHP beveiligen: PHP-sessiebeveiliging in de praktijk

> PHP-sessiebeveiliging stap voor stap: veilige php.ini-waarden, nieuwe sessie-ID na het inloggen, cookie-vlaggen en de PHP2Shell-aanwijzing over uploads.

Bron: https://phpsos.de/nl/ratgeber/session-sicherheit-php/  
Stand: 25-10-2026  
Aanbieder: PHP SOS, PHP-noodhulp en serverbeveiliging. Directe hulp via WhatsApp: https://wa.me/491736494914?text=Hallo%20PHP%20SOS%2C%20wij%20hebben%20hulp%20nodig%20met%20onze%20PHP-server.%20Bedrijf%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kort gezegd:** Voor PHP-sessiebeveiliging tellen vier dingen: veilige cookie-instellingen, een nieuwe sessie-ID na elke aanmelding, gescheiden en beschermde opslaglocaties en korte looptijden. Vanwege PHP2Shell moet bovendien `session.upload_progress.enabled=0` aan serverzijde zijn ingesteld, zolang er geen reden voor die functie is.

## Waarom sessies een aantrekkelijk doelwit zijn

Een sessie onthoudt dat een gebruiker is aangemeld. Technisch krijgt de browser daarvoor een willekeurige sessie-ID in een cookie en slaat de server de bijbehorende gegevens op. Wie deze ID kent, is voor de toepassing dezelfde gebruiker, zonder wachtwoord en zonder tweede factor.

Voor exploitanten betekent dit: een zwak sessiebeheer kan de inlogbeveiliging uitschakelen, ook als wachtwoorden en tweefactorauthenticatie netjes zijn uitgevoerd. Vooral kritiek is dat bij beheerdersgebieden, klantaccounts en portalen met persoonsgegevens.

De typische risico's zijn het onderscheppen van de sessie-ID (session hijacking), het onderschuiven van een bekende ID vóór het inloggen (session fixation) en te lang geldige sessies op gedeelde computers.

## Veilige basisinstellingen in de php.ini

De belangrijkste waarden kunt u centraal in de `php.ini` of per website in de FPM-pool instellen. In de pool gebruikt u `php_admin_value` of `php_admin_flag`, zodat de toepassing de waarden niet kan overschrijven.

```ini
; Basisverharding van sessies
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"

; PHP2Shell-aanwijzing van php.net, herstart daarna alle workers
session.upload_progress.enabled = 0
```

`use_strict_mode` zorgt ervoor dat PHP geen door de browser opgegeven, onbekende ID's overneemt. `cookie_httponly` verhindert dat JavaScript de ID kan lezen, wat de gevolgen van cross-site scripting beperkt. `cookie_secure` verstuurt het cookie alleen via HTTPS. `SameSite=Lax` bemoeilijkt cross-site-verzoeken met een geldige sessie; voor zuivere beheerdersgebieden kan `Strict` passen.

De looptijd van 1.800 seconden is een voorbeeldwaarde. Stel haar vast naar de beschermingsbehoefte: korter voor beheer en klantgegevens, langer alleen waar dat inhoudelijk nodig is.

Let erop dat `gc_maxlifetime` alleen vastlegt vanaf wanneer oude sessiegegevens mogen worden verwijderd. Op sommige distributies ruimt niet PHP zelf, maar een periodieke systeemtaak de sessiebestanden op. Controleer dan welke waarde die taak gebruikt, zodat uw instelling daadwerkelijk werkt. Vertrouw voor het afmelden na inactiviteit bovendien niet alleen op de opruimfunctie, maar controleer het tijdstip van de laatste actie in de toepassing zelf, zoals in het volgende voorbeeld.

## Sessie-ID na het inloggen vernieuwen

De belangrijkste regel in de code is de sessie-ID bij elke wijziging van de bevoegdheid opnieuw aan te maken, dus bij het inloggen, bij de overgang naar een beheerdersmodus en bij het uitloggen. Het volgende voorbeeld toont een netjes basispatroon met `password_verify()`:

```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; // dezelfde melding voor onjuist adres en onjuist wachtwoord
    }

    session_regenerate_id(true);           // nieuwe ID, oude verwerpen
    $_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();
}
```

Beveilig bovendien formulieren die iets wijzigen met een CSRF-token. Het wordt aangemaakt met `bin2hex(random_bytes(32))`, in de sessie opgeslagen en bij het verzenden met `hash_equals()` vergeleken.

## Opslaglocaties en scheiding van omgevingen

Standaard slaat PHP sessies als bestanden op in een gedeelde map. Op een server met meerdere websites kan dat betekenen dat een gecompromitteerde website de sessies van een andere kan lezen. Daarom hoort bij elke website een eigen pool met eigen gebruiker en eigen sessiepad.

```ini
; in de FPM-pool van de betreffende website
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
```

De map moet eigendom zijn van de poolgebruiker en de rechten `0700` hebben. Hoe u websites aanvullend van elkaar afschermt, beschrijft de gids [open_basedir en isolatie per website](https://phpsos.de/nl/ratgeber/open-basedir/). Worden sessies opgeslagen in Redis of een database, dan gelden dezelfde principes: eigen toegang per toepassing, geen open netwerktoegang.

## PHP-sessiebeveiliging en PHP2Shell

De PHP2Shell-kwetsbaarheden CVE-2026-109618 en CVE-2026-109619 zitten in PHP zelf. Als een van de tussenmaatregelen noemt php.net `session.upload_progress.enabled=0` in de php.ini, in de FPM-pool of in de serverconfiguratie in te stellen en daarna alle workers te herstarten. Uitdrukkelijk niet via `ini_set()` of een `.user.ini`.

Op 23-10-2026 volgde een melding over [CVE-2026-115560](https://phpsos.de/nl/php2shell/cve-2026-115560/). Mogelijke code-uitvoering bestaat daar alleen bij een ongebruikelijke combinatie van sessieserialisatie, omschakeling via `ini_set()` vóór `session_start()` en actieve upload progress. De tussenoplossing is dezelfde instelling, tot de volgende reguliere bugfixrelease.

> **Belangrijk:**
> Deze instelling is een tussenmaatregel en geen volledige bescherming. Beslissend blijft de update naar een verholpen PHP-versie en de daaropvolgende [herstart van alle PHP-workers](https://phpsos.de/nl/php2shell/php-fpm-neustart/).

Controleer na de wijziging welke waarde daadwerkelijk geldt. De opdrachtregel toont vaak een andere configuratie dan de webserver; veelzeggend is een beschermde, daarna weer verwijderde controlepagina of de poolconfiguratie zelf.

## Typische fouten bij sessies in PHP-toepassingen

Veel zwakke plekken ontstaan niet door ontbrekende instellingen, maar door de manier waarop toepassingen sessies gebruiken. Deze patronen moet u in de eigen code en bij dienstverleners gericht navragen:

- **Te veel in de sessie:** Wachtwoorden, volledige betaalgegevens of grote datasets horen niet in de sessie. Sla alleen op wat voor de toewijzing nodig is, bijvoorbeeld de gebruikers-ID en rollen.
- **Rechten alleen uit de sessie:** Wie beheerder is, moet bij kritieke acties tegen de database worden gecontroleerd. Anders blijven ingetrokken rechten tot het verlopen van de sessie werkzaam.
- **Sessie-ID in het adres:** ID's in URL's belanden in logs, browsergeschiedenis en referer-gegevens. `use_only_cookies` en `use_trans_sid = 0` voorkomen dat.
- **Geen einde van de sessie:** Ontbreekt een time-out bij inactiviteit, dan blijft een beheerder op een gedeelde computer onbeperkt aangemeld.
- **HTTPS achter een proxy verkeerd herkend:** Eindigt de versleuteling bij de load balancer, dan moet de toepassing dat weten. Anders zet ze cookies mogelijk zonder `Secure`-vlag.

Na een beveiligingsincident hoort daar ook bij dat alle bestaande sessies worden verworpen. Dat gaat het eenvoudigst door de inhoud van de sessiemap te wissen of de sessienaam te wijzigen. Alle gebruikers moeten zich dan opnieuw aanmelden en gestolen sessies zijn waardeloos.

## Hoe helpt PHP SOS concreet?

- We controleren sessie-instellingen, cookie-vlaggen en opslaglocaties op alle websites van een server en documenteren de daadwerkelijk werkzame waarden.
- We voeren de PHP2Shell-tussenmaatregelen per pool door, herstarten de workers gecontroleerd en tonen het resultaat aan.
- We scheiden websites met eigen pools en sessiepaden binnen de [serververharding](https://phpsos.de/nl/leistungen/server-haertung/).
- Voor maatwerkontwikkelingen controleren we inloggen, uitloggen en sessiewissel in de code en adviseren we concrete wijzigingen.


## Veelgestelde vragen

**Wat is session hijacking?**  
Bij session hijacking neemt een aanvaller de sessie van een aangemelde gebruiker over door diens sessie-ID te bemachtigen. Met de ID is hij voor de toepassing die gebruiker, zonder het wachtwoord te kennen. Veilige cookie-vlaggen en korte looptijden bemoeilijken dat aanzienlijk.

**Waarom moet de sessie-ID na het inloggen opnieuw worden aangemaakt?**  
Blijft de ID voor en na het inloggen gelijk, dan kan een tevoren bekende of ondergeschoven ID na de aanmelding blijven gelden. session_regenerate_id(true) maakt een nieuwe ID aan en verwerpt de oude.

**Kan ik session.upload_progress.enabled uitschakelen via ini_set()?**  
Nee, dat volstaat volgens php.net niet. De instelling hoort in de php.ini, de FPM-pool of de serverconfiguratie, niet in ini_set() of een .user.ini. Daarna moeten alle PHP-workers worden herstart.

**Waar moeten sessiebestanden worden opgeslagen?**  
In een map buiten de webroot die alleen de gebruiker van de betreffende PHP-pool mag lezen. Op servers met meerdere websites moet elke website een eigen sessiemap hebben.

## Bronnen

- [OWASP Cheat Sheet Series (Session Management)](https://cheatsheetseries.owasp.org/)
- [php.net: beveiligingsmeldingen over PHP2Shell](https://www.php.net/)
- [PHP 8 ChangeLog](https://www.php.net/ChangeLog-8.php)
