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.
; 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
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.
; 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. 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. 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.
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.
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_cookiesenuse_trans_sid = 0voorkomen 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.
- 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.