Kort sagt: CSRF i PHP forhindrer I ved, at hver forespørgsel, der ændrer noget, skal medbringe et hemmeligt token bundet til sessionen, som I kontrollerer på serversiden med hash_equals(). Desuden kontrollerer I headerne Origin eller Sec-Fetch-Site og sætter session-cookies med SameSite. GET-forespørgsler må aldrig ændre data, og loginformularen har også brug for beskyttelse.
Et klik, ingen lægger mærke til
En medarbejder er logget ind i shoppens backend og åbner en fremmed side i en anden fane. Denne side sender i baggrunden en formular til backenden, f.eks. for at ændre en e-mailadresse eller oprette en konto. Browseren hænger automatisk session-cookien på. For applikationen ser forespørgslen ud som enhver anden fra eget team.
Det er Cross-Site Request Forgery, kort CSRF: en forespørgsel, som en logget-ind bruger uvidende sender til en applikation, vedkommende stoler på. Angriberen ser ikke svaret, men har heller ikke brug for det. Det er nok, at handlingen udføres.
CSRF står ikke i overskrifterne som PHP2Shell, men er et hyppigt fund i selvudviklede PHP-applikationer. Frameworks som Symfony og Laravel medbringer en beskyttelse; sårbarheder opstår dér især, hvor den er slået fra for enkelte ruter. Egenudviklinger uden framework har ofte slet ingen.
CSRF i PHP: Hvilke forespørgsler skal beskyttes
Beskyttelse har alle forespørgsler brug for, der ændrer en tilstand: gemme, slette, bestille, tildele rettigheder, ændre indstillinger, uploade filer, logge ud. Læsende kald behøver ikke noget token, så længe de virkelig kun læser.
Heraf følger den første regel: Ingen ændring via GET. Links som loeschen.php?id=… kan især let udløses fra fremmede sider, f.eks. via et indlejret billede. Ændrende handlinger hører hjemme i POST, PUT, PATCH eller DELETE.
Den anden regel: Beskyt også login. Ved såkaldt login-CSRF bliver et offer ubemærket logget ind på en konto, angriberen ejer. Indtaster offeret dér betalingsdata eller dokumenter, havner disse hos angriberen. Et token i loginformularen og et nyt session-ID efter vellykket login forhindrer det.
Synchronizer-token-mønstret i PHP
Til klassiske applikationer med sessioner anbefaler OWASP synchronizer-token-mønstret: Serveren genererer et tilfældigt token, gemmer det i sessionen og forventer det tilbage i hver ændrende formular. Vigtigt er, at tokenet sendes med i formularen eller i en egen header, ikke i en cookie.
<?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') ?>">
<!-- flere felter -->
</form>
hash_equals() sammenligner i konstant tid, så svartiden intet afslører om den rigtige værdi. Efter login bør I fornye session-ID'et med session_regenerate_id(true) og generere et nyt token. Hvorfor random_bytes() er den rigtige kilde, og hvilke funktioner der er uegnede, forklarer Sikre tilfældige tal og tokens i PHP.
Ét token pr. session er nok til de fleste applikationer. Tokens pr. formular eller pr. forespørgsel øger beskyttelsen en smule, men fører til problemer med flere åbne faner og tilbage-knappen. Vigtigere end hyppigheden af skiftet er, at tokenet aldrig optræder i URL'er, hvor det kan havne i logge, i browserhistorikken og i referrer-oplysninger til fremmede sider.
Til tilstandsløse applikationer uden session nævner OWASP som alternativ den signerede double-submit-cookie: et token, der er bundet til sessionen med HMAC. Den enkle, usignerede variant anbefales udtrykkeligt ikke.
Andet lag: Origin, Sec-Fetch-Site og SameSite
Moderne browsere sender ved forespørgsler oplysninger om deres oprindelse. Headeren Origin nævner den kaldende side, Sec-Fetch-Site angiver, om forespørgslen stammer fra den samme side (same-origin), en beslægtet (same-site) eller en fremmed (cross-site). Heraf kan der bygges en enkel ekstra kontrol:
<?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 fremhæver her to punkter: Oprindelsen skal sammenlignes fuldstændigt, så domæner med lignende navne ikke slipper igennem, og en manglende header eller værdien null må ikke gælde som egen oprindelse. Fordi ældre browsere ikke altid sender headerne, forbliver tokenet hovedforsvaret.
Det tredje lag er session-cookies med SameSite. PHP understøtter session.cookie_samesite siden version 7.3, standardværdien er tom. Sæt mindst Lax, for rene backends er Strict ofte muligt. De øvrige cookie-indstillinger beskriver vejledningen Sikr sessioner i PHP.
Bruger I et CMS, medbringer det som regel egne værktøjer. WordPress arbejder f.eks. med såkaldte nonces, som plugins indbygger via funktioner som wp_nonce_field() og check_admin_referer(). Sårbarheder opstår i praksis især i udvidelser, der glemmer sådanne kontroller ved enkelte handlinger. Kontrollér derfor særligt selvskrevne eller lidt udbredte plugins.
Ved grænseflader, som browsere bruger fra andre domæner, afgør CORS-konfigurationen desuden. Returnerer serveren enhver forespørgende oprindelse som tilladt og tillader samtidig cookies, sætter den selv browserens beskyttelse ud af kraft. OWASP anbefaler kun at åbne CORS for udtrykkeligt betroede oprindelser; en generel frigivelse af alle subdomæner bliver et problem, så snart et af dem falder i fremmede hænder.
Tjekliste til eksisterende applikationer
Ved gamle projekter hjælper en systematisk gennemgang:
- Registrér alle ruter og scripts, der ændrer data, også Ajax-endepunkter og filuploads.
- Kontrollér, om der bruges GET til det, og omstil til POST eller passende metoder.
- Påvis for hver ændrende rute, at et token kontrolleres; søg målrettet efter undtagelser i frameworket.
- Test login, logout og ændring af adgangskode for sig.
- Tilføj
session.cookie_samesiteog oprindelseskontrollen. - Udbedr XSS-sårbarheder, for indsmuglet kode kan læse ethvert token. Fremgangsmåden beskriver Forhindr cross-site scripting i PHP.
En CSRF-beskyttelse, der kun kontrolleres i frontend med JavaScript, er virkningsløs. Kontrollen skal ske på serveren, før handlingen udføres.
Hvordan hjælper PHP SOS konkret?
- Vi registrerer alle ændrende ruter i jeres PHP-applikation og kontrollerer, hvor et token mangler eller er slået fra.
- Vi indbygger et centralt kontrolpunkt i stedet for at fordele token-kontroller over mange scripts.
- Vi tilføjer oprindelseskontrol og SameSite-indstillinger uden at forstyrre betalings- eller loginforløb.
- Vi tester igen efter omstillingen og dokumenterer resultaterne.
En fuldstændig kontrol af jeres applikation beskriver ydelsen Sikkerhedsaudit og penetrationstest.
Ofte stillede spørgsmål
Er SameSite=Lax nok som CSRF-beskyttelse?
Nej. SameSite-cookies er ifølge OWASP et ekstra lag, ikke en erstatning. Lax sender fortsat cookies ved normale sidekald via GET, og angreb fra subdomæner på det samme website dækkes ikke.
Har JSON-API'er brug for et CSRF-token?
Hvis API'et autentificerer med cookies, ja, eller en tilsvarende kontrol. Et alternativ er egne forespørgsels-headere, som browsere kun tillader fra fremmede sider efter en CORS-forhåndskontrol. Så må CORS-frigivelsen dog kun indeholde udtrykkeligt betroede oprindelser.
Skal også loginformularen beskyttes?
Ja. Ved login-CSRF bliver et offer ubemærket logget ind på en fremmed konto og indtaster dér egne data. Et token i loginformularen og en ny session efter login forhindrer det.
Beskytter CSRF-beskyttelse også mod XSS?
Omvendt: En XSS-sårbarhed sætter enhver CSRF-beskyttelse ud af kraft, fordi indsmuglet kode i sidens navn kan læse og medsende gyldige tokens. Begge svagheder skal udbedres hver for sig.