# CSRF-skydd i PHP-applikationer

> Genomför CSRF-skydd i PHP: synchronizer-token, kontroll av Origin och Sec-Fetch-Site, SameSite-cookies och login-CSRF. Med kodexempel och checklista.

Källa: https://phpsos.de/sv/blog/csrf-schutz-php/  
Aktualitet: 2026-10-25  
Leverantör: PHP SOS, PHP-nödhjälp och serversäkerhet. Omedelbar hjälp via WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20beh%C3%B6ver%20hj%C3%A4lp%20med%20v%C3%A5r%20PHP-server.%20F%C3%B6retag%3A%20%E2%80%A6%20Webbplats%3A%20%E2%80%A6

**Kort sagt:** CSRF i PHP förhindrar ni genom att varje förfrågan som ändrar något måste ha med sig ett hemligt, till sessionen bundet token som ni kontrollerar på serversidan med `hash_equals()`. Dessutom kontrollerar ni headerna `Origin` eller `Sec-Fetch-Site` och sätter sessionscookies med `SameSite`. GET-förfrågningar får aldrig ändra data, och inloggningsformuläret behöver också skydd.

## Ett klick som ingen märker

En medarbetare är inloggad i butikens backend och öppnar i en andra flik en främmande sida. Den sidan skickar i bakgrunden ett formulär till backend, till exempel för att ändra en e-postadress eller skapa ett konto. Webbläsaren bifogar sessionscookien automatiskt. För applikationen ser förfrågan ut som vilken annan som helst från det egna teamet.

Det är Cross-Site Request Forgery, kort CSRF: en förfrågan som en inloggad användare omedvetet skickar till en applikation han eller hon litar på. Angriparen ser inte svaret, men behöver det inte heller. Det räcker att åtgärden utförs.

CSRF hamnar inte i rubrikerna som PHP2Shell men är ett vanligt fynd i egenutvecklade PHP-applikationer. Ramverk som Symfony och Laravel har ett skydd med sig; luckor uppstår där framför allt när det har stängts av för enskilda routes. Egenutvecklingar utan ramverk saknar ofta helt skydd.

## CSRF i PHP: vilka förfrågningar som måste skyddas

Skydd behövs för alla förfrågningar som ändrar ett tillstånd: spara, radera, beställa, tilldela rättigheter, ändra inställningar, ladda upp filer, logga ut. Läsande anrop behöver inget token så länge de verkligen bara läser.

Därav följer den första regeln: **Ingen ändring via GET.** Länkar som `loeschen.php?id=…` är särskilt lätta att utlösa från främmande sidor, till exempel via en inbäddad bild. Förändrande åtgärder hör hemma i POST, PUT, PATCH eller DELETE.

Den andra regeln: **Skydda även inloggningen.** Vid så kallad login-CSRF loggas ett offer omärkligt in på ett konto som tillhör angriparen. Matar offret där in betalningsuppgifter eller dokument hamnar de hos angriparen. Ett token i inloggningsformuläret och ett nytt sessions-ID efter lyckad inloggning förhindrar det.

## Synchronizer-token-mönstret i PHP

För klassiska applikationer med sessioner rekommenderar OWASP synchronizer-token-mönstret: Servern skapar ett slumpmässigt token, lagrar det i sessionen och förväntar sig det tillbaka i varje förändrande formulär. Viktigt är att tokenet skickas med i formuläret eller i en egen header, inte i en cookie.

```php
<?php
declare(strict_types=1);

function csrfToken(): string
{
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function csrfPruefen(): void
{
    $gesendet = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
    if (!is_string($gesendet) || !hash_equals(csrfToken(), $gesendet)) {
        http_response_code(403);
        exit('Anfrage abgelehnt.');
    }
}

if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
    csrfPruefen();
}
?>
<form method="post" action="/konto/email">
    <input type="hidden" name="csrf_token"
           value="<?= htmlspecialchars(csrfToken(), ENT_QUOTES, 'UTF-8') ?>">
    <!-- fler fält -->
</form>
```

`hash_equals()` jämför i konstant tid, så att svarstiden inte avslöjar något om det rätta värdet. Efter inloggningen bör ni förnya sessions-ID:t med `session_regenerate_id(true)` och skapa ett nytt token. Varför `random_bytes()` är rätt källa och vilka funktioner som är olämpliga förklarar [Säkra slumptal och tokens i PHP](https://phpsos.de/sv/blog/sichere-tokens-php/).

Ett token per session räcker för de flesta applikationer. Tokens per formulär eller per förfrågan höjer skyddet något men ger problem med flera öppna flikar och tillbakaknappen. Viktigare än hur ofta det byts är att tokenet aldrig dyker upp i URL:er, där det kan hamna i loggar, i webbläsarhistoriken och i referrer-uppgifter till främmande sidor.

För tillståndslösa applikationer utan session nämner OWASP som alternativ den signerade double-submit-cookien: ett token som är bundet till sessionen via HMAC. Den enkla, osignerade varianten rekommenderas uttryckligen inte.

## Andra lagret: Origin, Sec-Fetch-Site och SameSite

Moderna webbläsare skickar vid förfrågningar information om sitt ursprung. Headern `Origin` anger den anropande sidan, `Sec-Fetch-Site` anger om förfrågan kommer från samma sida (`same-origin`), en närstående (`same-site`) eller en främmande (`cross-site`). Av det går det att bygga en enkel extrakontroll:

```php
<?php
$erlaubteHerkunft = 'https://www.example.de';
$fetchSite = $_SERVER['HTTP_SEC_FETCH_SITE'] ?? null;
$origin    = $_SERVER['HTTP_ORIGIN'] ?? null;

$verdaechtig = ($fetchSite !== null && $fetchSite !== 'same-origin')
    || ($fetchSite === null && $origin !== null && $origin !== $erlaubteHerkunft);

if ($_SERVER['REQUEST_METHOD'] !== 'GET' && $verdaechtig) {
    http_response_code(403);
    exit;
}
```

OWASP betonar här två punkter: Ursprunget måste jämföras fullständigt så att liknande domäner inte slinker igenom, och en saknad header eller värdet `null` får inte räknas som eget ursprung. Eftersom äldre webbläsare inte alltid skickar headerna förblir tokenet det främsta försvaret.

Det tredje lagret är sessionscookies med `SameSite`. PHP stöder `session.cookie_samesite` sedan version 7.3, standardvärdet är tomt. Sätt minst `Lax`, för rena backends är `Strict` ofta möjligt. De övriga cookieinställningarna beskriver guiden [Säkra sessioner i PHP](https://phpsos.de/sv/ratgeber/session-sicherheit-php/).

Använder ni ett CMS har det oftast egna verktyg med sig. WordPress arbetar till exempel med så kallade nonces, som tillägg binder in via funktioner som `wp_nonce_field()` och `check_admin_referer()`. Luckor uppstår i praktiken framför allt i tillägg som glömmer sådana kontroller vid enskilda åtgärder. Granska därför särskilt egenskrivna eller föga spridda tillägg.

För gränssnitt som webbläsare använder från andra domäner avgör dessutom CORS-konfigurationen. Returnerar servern varje anropande ursprung som tillåtet och tillåter samtidigt cookies sätter den själv webbläsarens skydd ur spel. OWASP rekommenderar att öppna CORS bara för uttryckligen betrodda ursprung; en generell frigivning av alla underdomäner blir ett problem så snart någon av dem hamnar i fel händer.

## Checklista för befintliga applikationer

För projekt som vuxit över tid hjälper en systematisk genomgång:

1. Registrera alla routes och skript som ändrar data, även Ajax-slutpunkter och filuppladdningar.
2. Kontrollera om GET används för dem och ställ om till POST eller lämpliga metoder.
3. För varje förändrande route, belägg att ett token kontrolleras; sök gärna efter undantag i ramverket.
4. Testa inloggning, utloggning och lösenordsändring separat.
5. Komplettera `session.cookie_samesite` och ursprungskontrollen.
6. Åtgärda XSS-sårbarheter, för inplanterad kod kan läsa varje token. Tillvägagångssättet beskriver [Förhindra cross-site scripting i PHP](https://phpsos.de/sv/ratgeber/xss-php/).

> **Viktigt:**
> Ett CSRF-skydd som bara kontrolleras i frontend med JavaScript är verkningslöst. Kontrollen måste ske på servern innan åtgärden utförs.

## Hur hjälper PHP SOS konkret?

- Vi registrerar alla förändrande routes i er PHP-applikation och kontrollerar var ett token saknas eller har stängts av.
- Vi bygger in en central kontrollpunkt i stället för att sprida tokenkontroller över många skript.
- Vi kompletterar ursprungskontroll och SameSite-inställningar utan att störa betalnings- eller inloggningsflöden.
- Vi testar på nytt efter omställningen och dokumenterar resultaten.

En fullständig granskning av er applikation beskriver tjänsten [säkerhetsaudit och penetrationstest](https://phpsos.de/sv/leistungen/sicherheitsaudit-penetrationstest/).


## Vanliga frågor

**Räcker SameSite=Lax som CSRF-skydd?**  
Nej. SameSite-cookies är enligt OWASP ett extra lager, inte en ersättning. Lax skickar fortfarande cookies vid vanliga sidanrop via GET, och angrepp från underdomäner till samma webbplats täcks inte.

**Behöver JSON-API:er ett CSRF-token?**  
Om API:et autentiserar med cookies, ja, eller en likvärdig kontroll. Ett alternativ är egna förfrågningsheaders, som webbläsare tillåter för främmande sidor först efter en CORS-förkontroll. Då får CORS-frigivningen dock bara innehålla uttryckligen betrodda ursprung.

**Måste även inloggningsformuläret skyddas?**  
Ja. Vid login-CSRF loggas ett offer omärkligt in på ett främmande konto och matar där in egna uppgifter. Ett token i inloggningsformuläret och en ny session efter inloggningen förhindrar det.

**Skyddar CSRF-skydd även mot XSS?**  
Tvärtom: En XSS-sårbarhet sätter varje CSRF-skydd ur spel, eftersom inplanterad kod i sidans namn kan läsa giltiga tokens och skicka med dem. Båda svagheterna måste åtgärdas var för sig.

## Källor

- [OWASP Cross-Site Request Forgery Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html)
- [PHP-manualen: sessionskonfiguration](https://www.php.net/manual/de/session.configuration.php)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
