Kort sagt: For PHP-sessionsikkerheden tæller fire ting: sikre cookie-indstillinger, et nyt session-ID efter hver tilmelding, adskilte og beskyttede lagringssteder samt korte levetider. På grund af PHP2Shell bør desuden session.upload_progress.enabled=0 være sat på serversiden, så længe der ikke er grund til at bruge denne funktion.
Hvorfor sessioner er et attraktivt mål
En session husker, at en bruger er logget ind. Teknisk får browseren dertil et tilfældigt session-ID i en cookie, og serveren gemmer de tilhørende data. Den, der kender dette ID, er for applikationen den samme bruger, uden adgangskode og uden anden faktor.
For driftsansvarlige betyder det: En svag sessionsstyring kan sætte login-beskyttelsen ud af kraft, selv om adgangskoder og tofaktor-login er gennemført rent. Særligt kritisk er det ved admin-områder, kundekonti og portaler med personoplysninger.
De typiske risici er at få fat i session-ID'et (session hijacking), at få indsmuglet et kendt ID før login (session fixation) og for længe gyldige sessioner på delte computere.
Sikre grundindstillinger i php.ini
De vigtigste værdier kan sættes centralt i php.ini eller pr. website i FPM-poolen. I poolen bruger I php_admin_value eller php_admin_flag, så applikationen ikke kan overskrive værdierne.
; Grundhærdning af sessioner
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-henvisning fra php.net, genstart derefter alle workere
session.upload_progress.enabled = 0
use_strict_mode sørger for, at PHP ikke overtager ukendte ID'er, som browseren foreskriver. cookie_httponly forhindrer, at JavaScript kan læse ID'et, hvilket begrænser følgerne af cross-site scripting. cookie_secure sender kun cookien via HTTPS. SameSite=Lax gør cross-site-forespørgsler med gyldig session vanskeligere; for rene admin-områder kan Strict passe.
Levetiden på 1.800 sekunder er en eksempelværdi. Fastlæg den efter beskyttelsesbehovet: kortere for administration og kundedata, længere kun dér, hvor det er fagligt nødvendigt.
Bemærk, at gc_maxlifetime kun fastlægger, fra hvornår gamle sessionsdata må slettes. På nogle distributioner er det ikke PHP selv, men et regelmæssigt systemjob, der rydder sessionsfilerne op. Kontrollér da, hvilken værdi dette job bruger, så jeres indstilling faktisk griber. Stol desuden ved afmelding efter inaktivitet ikke alene på oprydningsfunktionen, men kontrollér tidspunktet for den sidste handling i selve applikationen, som vist i det følgende eksempel.
Forny session-ID'et efter login
Den vigtigste regel i koden er at generere session-ID'et på ny ved hvert skift af rettighed, altså ved login, ved skift til en admin-tilstand og ved logout. Det følgende eksempel viser et rent grundmønster med 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; // samme meddelelse for forkert adresse og forkert adgangskode
}
session_regenerate_id(true); // nyt ID, kassér det gamle
$_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();
}
Beskyt desuden formularer, der ændrer noget, med et CSRF-token. Det genereres med bin2hex(random_bytes(32)), lægges i sessionen og sammenlignes ved afsendelsen med hash_equals().
Lagringssteder og adskillelse af kunder
Som standard lægger PHP sessioner som filer i en fælles mappe. På en server med flere websites kan det betyde, at et kompromitteret website kan læse et andets sessioner. Derfor hører der til hvert website en egen pool med egen bruger og egen sessionssti.
; i FPM-poolen for det pågældende website
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a
Mappen bør tilhøre pool-brugeren og have rettighederne 0700. Hvordan I yderligere skærmer websites af fra hinanden, beskriver vejledningen open_basedir og isolation pr. website. Gemmes sessioner i Redis eller en database, gælder de samme principper: egen adgang pr. applikation, ingen åben netværksadgang.
PHP-sessionsikkerhed og PHP2Shell
PHP2Shell-sårbarhederne CVE-2026-109618 og CVE-2026-109619 ligger i selve PHP. Som en af overgangsforanstaltningerne nævner php.net at sætte session.upload_progress.enabled=0 i php.ini, i FPM-poolen eller i serverkonfigurationen og derefter genstarte alle workere. Udtrykkeligt ikke via ini_set() eller en .user.ini.
Den 23.10.2026 fulgte en meddelelse om CVE-2026-115560. En mulig kodekørsel består dér kun ved en usædvanlig kombination af sessionsserialisering, omstilling via ini_set() før session_start() og aktiv upload-progress. Afhjælpningen er den samme indstilling, indtil den næste almindelige bugfix-release.
Denne indstilling er en overgangsforanstaltning og ikke en fuldstændig beskyttelse. Afgørende forbliver opdateringen til en rettet PHP-version og den efterfølgende genstart af alle PHP-workere.
Kontrollér efter ændringen, hvilken værdi der faktisk gælder. Kommandolinjen viser ofte en anden konfiguration end webserveren; sigende er en beskyttet, derefter igen slettet kontrolside eller selve pool-konfigurationen.
Typiske fejl ved sessioner i PHP-applikationer
Mange svagheder opstår ikke på grund af manglende indstillinger, men på grund af den måde, hvorpå applikationer bruger sessioner. Disse mønstre bør I målrettet forhøre jer om i egen kode og hos tjenesteudbydere:
- For meget i sessionen: Adgangskoder, fuldstændige betalingsdata eller store datasæt hører ikke hjemme i sessionen. Gem kun det, der er nødvendigt til tilknytningen, f.eks. bruger-ID og roller.
- Rettigheder kun fra sessionen: Hvem der er admin, bør ved kritiske handlinger kontrolleres mod databasen. Ellers forbliver fratagne rettigheder virksomme, til sessionen udløber.
- Session-ID i adressen: ID'er i URL'er havner i logs, browserhistorik og referer-oplysninger.
use_only_cookiesoguse_trans_sid = 0forhindrer det. - Ingen afslutning af sessionen: Mangler der en timeout ved inaktivitet, forbliver en admin på en delt computer logget ind ubegrænset.
- HTTPS bag en proxy genkendes forkert: Slutter krypteringen ved load balanceren, skal applikationen vide det. Ellers sætter den måske cookies uden
Secure-flag.
Efter en sikkerhedshændelse hører det desuden til at kassere alle bestående sessioner. Det sker enklest ved at slette indholdet af sessionsmappen eller ændre sessionsnavnet. Alle brugere må så tilmelde sig på ny, og stjålne sessioner er værdiløse.
Hvordan hjælper PHP SOS konkret?
- Vi gennemgår sessionsindstillinger, cookie-flags og lagringssteder på alle websites på en server og dokumenterer de faktisk virksomme værdier.
- Vi gennemfører PHP2Shell-overgangsforanstaltningerne pr. pool, genstarter workerne kontrolleret og dokumenterer resultatet.
- Vi adskiller websites med egne pools og sessionsstier inden for rammerne af serverhærdningen.
- Ved egenudviklinger gennemgår vi login, logout og sessionsskift i koden og anbefaler konkrete ændringer.
Ofte stillede spørgsmål
Hvad er session hijacking?
Ved session hijacking overtager en angriber en logget ind brugers session ved at få fat i vedkommendes session-ID. Med ID'et er angriberen for applikationen denne bruger, uden at kende adgangskoden. Sikre cookie-flags og korte levetider gør det tydeligt vanskeligere.
Hvorfor skal session-ID'et genereres på ny efter login?
Forbliver ID'et det samme før og efter login, kunne et forud kendt eller indsmuglet ID fortsat gælde efter tilmeldingen. session_regenerate_id(true) genererer et nyt ID og kasserer det gamle.
Kan jeg slå session.upload_progress.enabled fra via ini_set()?
Nej, det er ifølge php.net ikke nok. Indstillingen hører hjemme i php.ini, FPM-poolen eller serverkonfigurationen, ikke i ini_set() eller en .user.ini. Derefter skal alle PHP-workere genstartes.
Hvor bør sessionsfiler gemmes?
I en mappe uden for webroot, som kun brugeren i den pågældende PHP-pool må læse. På servere med flere websites bør hvert website have en egen sessionsmappe.