Aktualna sytuacja: PHP2Shell. Dwie krytyczne luki w PHP (CVSS 9,2) umożliwiają wykonanie złośliwego kodu bez logowania. Poprawki od 21.10.2026.Co należy teraz zrobić →

Zabezpieczanie sesji w PHP: bezpieczeństwo sesji PHP w praktyce

Sesje utrzymują użytkowników zalogowanych i są przez to atrakcyjnym celem. Jak bezpiecznie skonfigurować ciasteczka, identyfikatory sesji i miejsca zapisu oraz wdrożyć wskazówkę PHP2Shell o upload progress.

Stan 5 min czytaniaPoradniki

Krótko: Dla bezpieczeństwa sesji PHP liczą się cztery rzeczy: bezpieczne ustawienia ciasteczek, nowy identyfikator sesji po każdym logowaniu, rozdzielone i chronione miejsca zapisu oraz krótkie czasy ważności. Ze względu na PHP2Shell po stronie serwera powinno być ponadto ustawione session.upload_progress.enabled=0, o ile nie ma powodu, by korzystać z tej funkcji.

Dlaczego sesje są atrakcyjnym celem

Sesja zapamiętuje, że użytkownik jest zalogowany. Technicznie przeglądarka otrzymuje w tym celu losowy identyfikator sesji w ciasteczku, a serwer zapisuje powiązane dane. Kto zna ten identyfikator, jest dla aplikacji tym samym użytkownikiem, bez hasła i bez drugiego składnika.

Dla operatorów oznacza to: słabe zarządzanie sesjami może obejść ochronę logowania, nawet jeśli hasła i logowanie dwuskładnikowe są prawidłowo wdrożone. Szczególnie krytyczne jest to w obszarach administracyjnych, kontach klientów i portalach z danymi osobowymi.

Typowe ryzyka to przechwycenie identyfikatora sesji (session hijacking), podsunięcie znanego identyfikatora przed logowaniem (session fixation) oraz zbyt długo ważne sesje na współdzielonych komputerach.

Bezpieczne ustawienia podstawowe w php.ini

Najważniejsze wartości można ustawić centralnie w php.ini lub dla każdej strony w puli FPM. W puli używają Państwo php_admin_value lub php_admin_flag, aby aplikacja nie mogła nadpisać tych wartości.

; Podstawowe utwardzenie sesji
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"

; Wskazówka PHP2Shell z php.net, potem zrestartować wszystkie procesy robocze
session.upload_progress.enabled = 0

use_strict_mode sprawia, że PHP nie przyjmuje narzuconych przez przeglądarkę, nieznanych identyfikatorów. cookie_httponly uniemożliwia JavaScriptowi odczytanie identyfikatora, co ogranicza skutki cross-site scriptingu. cookie_secure wysyła ciasteczko tylko przez HTTPS. SameSite=Lax utrudnia żądania cross-site z ważną sesją; dla czysto administracyjnych obszarów może pasować Strict.

Czas 1800 sekund to wartość przykładowa. Proszę ustalić go według potrzeb ochrony: krócej dla administracji i danych klientów, dłużej tylko tam, gdzie jest to merytorycznie konieczne.

Proszę pamiętać, że gc_maxlifetime określa tylko, od kiedy stare dane sesji mogą zostać usunięte. W niektórych dystrybucjach pliki sesji sprząta nie sam PHP, lecz regularne zadanie systemowe. Proszę wtedy sprawdzić, jakiej wartości używa to zadanie, aby Państwa ustawienie faktycznie działało. Przy wylogowaniu po bezczynności nie należy ponadto polegać wyłącznie na funkcji sprzątającej, lecz sprawdzać czas ostatniej akcji w samej aplikacji, jak pokazano w poniższym przykładzie.

Odnawianie identyfikatora sesji po logowaniu

Najważniejszą zasadą w kodzie jest generowanie nowego identyfikatora sesji przy każdej zmianie uprawnień, czyli przy logowaniu, przy przejściu do trybu administratora i przy wylogowaniu. Poniższy przykład pokazuje czysty wzorzec podstawowy z 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; // ten sam komunikat dla błędnego adresu i błędnego hasła
    }

    session_regenerate_id(true);           // nowy identyfikator, stary odrzucić
    $_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();
}

Uzupełniająco formularze, które coś zmieniają, chronią Państwo tokenem CSRF. Generuje się go za pomocą bin2hex(random_bytes(32)), zapisuje w sesji i przy wysyłaniu porównuje z hash_equals().

Miejsca zapisu i rozdzielenie klientów

Domyślnie PHP zapisuje sesje jako pliki we wspólnym katalogu. Na serwerze z kilkoma stronami może to oznaczać, że skompromitowana strona może czytać sesje innej. Dlatego do każdej strony należy osobna pula z własnym użytkownikiem i własną ścieżką sesji.

; w puli FPM danej strony
php_admin_value[session.save_path] = /var/lib/php/sessions/kunde-a

Katalog powinien należeć do użytkownika puli i mieć uprawnienia 0700. Jak dodatkowo odgrodzić strony od siebie, opisuje poradnik open_basedir i izolacja każdej strony. Jeśli sesje są przechowywane w Redis lub bazie danych, obowiązują te same zasady: osobny dostęp dla każdej aplikacji, brak otwartego dostępu sieciowego.

Bezpieczeństwo sesji PHP a PHP2Shell

Luki PHP2Shell CVE-2026-109618 i CVE-2026-109619 leżą w samym PHP. Jako jeden ze środków przejściowych php.net podaje ustawienie session.upload_progress.enabled=0 w php.ini, w puli FPM lub w konfiguracji serwera, a następnie restart wszystkich procesów roboczych. Wyraźnie nie przez ini_set() ani .user.ini.

23.10.2026 pojawił się komunikat o CVE-2026-115560. Możliwe wykonanie kodu istnieje tam tylko przy nietypowym połączeniu serializacji sesji, przełączenia przez ini_set() przed session_start() i aktywnego upload progress. Środek zaradczy to to samo ustawienie, do następnego regularnego wydania z poprawkami błędów.

Ważne

To ustawienie jest środkiem przejściowym, a nie pełną ochroną. Rozstrzygająca pozostaje aktualizacja do naprawionej wersji PHP i następujący po niej restart wszystkich procesów roboczych PHP.

Po zmianie proszę sprawdzić, jaka wartość faktycznie obowiązuje. Wiersz poleceń często pokazuje inną konfigurację niż serwer WWW; miarodajna jest chroniona, a potem ponownie usunięta strona kontrolna albo sama konfiguracja puli.

Typowe błędy w sesjach aplikacji PHP

Wiele podatności powstaje nie z powodu brakujących ustawień, lecz przez sposób, w jaki aplikacje używają sesji. O te wzorce należy celowo pytać we własnym kodzie i u usługodawców:

  • Zbyt wiele w sesji: hasła, pełne dane płatnicze czy duże zbiory danych nie należą do sesji. Proszę zapisywać tylko to, co potrzebne do przypisania, na przykład identyfikator użytkownika i role.
  • Uprawnienia wyłącznie z sesji: kto jest administratorem, powinien być przy krytycznych akcjach sprawdzany w bazie danych. W przeciwnym razie odebrane uprawnienia pozostają skuteczne do wygaśnięcia sesji.
  • Identyfikator sesji w adresie: identyfikatory w adresach URL trafiają do logów, historii przeglądarki i danych referera. use_only_cookies i use_trans_sid = 0 temu zapobiegają.
  • Brak końca sesji: bez limitu czasu bezczynności administrator na współdzielonym komputerze pozostaje zalogowany bez ograniczeń.
  • HTTPS za proxy rozpoznawane błędnie: jeśli szyfrowanie kończy się na load balancerze, aplikacja musi o tym wiedzieć. W przeciwnym razie może ustawiać ciasteczka bez flagi Secure.

Po incydencie bezpieczeństwa należy ponadto odrzucić wszystkie istniejące sesje. Najprościej zrobić to, usuwając zawartość katalogu sesji lub zmieniając nazwę sesji. Wszyscy użytkownicy muszą wtedy zalogować się na nowo, a skradzione sesje tracą wartość.

Jak konkretnie pomaga PHP SOS?

  • Sprawdzamy ustawienia sesji, flagi ciasteczek i miejsca zapisu na wszystkich stronach serwera i dokumentujemy faktycznie obowiązujące wartości.
  • Wdrażamy środki przejściowe PHP2Shell dla każdej puli, kontrolowanie restartujemy procesy robocze i potwierdzamy wynik.
  • Rozdzielamy strony za pomocą osobnych pul i ścieżek sesji w ramach utwardzania serwera.
  • W przypadku własnych rozwiązań sprawdzamy w kodzie logowanie, wylogowanie i zmianę sesji oraz rekomendujemy konkretne zmiany.

Częste pytania

Czym jest przejęcie sesji (session hijacking)?

Przy przejęciu sesji atakujący przejmuje sesję zalogowanego użytkownika, zdobywając jego identyfikator sesji. Z tym identyfikatorem jest dla aplikacji tym użytkownikiem, nie znając hasła. Bezpieczne flagi ciasteczek i krótkie czasy ważności wyraźnie to utrudniają.

Dlaczego identyfikator sesji trzeba wygenerować od nowa po logowaniu?

Jeśli identyfikator jest taki sam przed logowaniem i po nim, wcześniej znany lub podsunięty identyfikator mógłby obowiązywać dalej po zalogowaniu. session_regenerate_id(true) tworzy nowy identyfikator i odrzuca stary.

Czy mogę wyłączyć session.upload_progress.enabled przez ini_set()?

Nie, według php.net to nie wystarcza. Ustawienie należy do php.ini, puli FPM lub konfiguracji serwera, a nie do ini_set() ani .user.ini. Potem trzeba zrestartować wszystkie procesy robocze PHP.

Gdzie należy zapisywać pliki sesji?

W katalogu poza webrootem, który może czytać tylko użytkownik danej puli PHP. Na serwerach z kilkoma stronami każda strona powinna mieć własny katalog sesji.

Źródła

Kontakt natychmiast

Liczy się każda godzina. Proszę napisać do nas teraz.

Proszę krótko opisać system, wersję PHP i obserwacje. Skontaktujemy się z Państwem, ustalimy sytuację i będziemy pracować zdalnie, aż incydent zostanie zamknięty i udokumentowany.

Natychmiastowa pomoc