Actuele situatie: PHP2Shell. Twee kritieke PHP-kwetsbaarheden (CVSS 9.2) maken schadelijke code zonder inloggen mogelijk. Patches sinds 21-10-2026.Wat u nu moet doen →

CSRF-bescherming in PHP-applicaties

Hoe ontwikkelaars cross-site request forgery in PHP-applicaties voorkomen: tokens voor elk wijzigend verzoek, herkomstcontrole in de header en SameSite-cookies als extra laag.

Stand 6 min. leestijdVeilig ontwikkelen

Kort gezegd: CSRF voorkomt u in PHP door te eisen dat elk verzoek dat iets wijzigt een geheim, aan de sessie gebonden token meebrengt, dat u aan de serverkant controleert met hash_equals(). Daarnaast controleert u de headers Origin of Sec-Fetch-Site en stelt u sessiecookies in met SameSite. GET-verzoeken mogen nooit gegevens wijzigen en ook het inlogformulier heeft bescherming nodig.

Een klik die niemand opmerkt

Een medewerkster is aangemeld in de backend van de webshop en opent in een tweede tabblad een vreemde pagina. Die pagina stuurt op de achtergrond een formulier naar de backend, bijvoorbeeld om een e-mailadres te wijzigen of een account aan te maken. De browser hangt het sessiecookie automatisch eraan. Voor de applicatie ziet het verzoek eruit als elk ander verzoek uit het eigen team.

Dat is cross-site request forgery, kortweg CSRF: een verzoek dat een aangemelde gebruiker onwetend verstuurt naar een applicatie die hij vertrouwt. De aanvaller ziet het antwoord niet, maar hij heeft het ook niet nodig. Het volstaat dat de actie wordt uitgevoerd.

CSRF haalt niet de krantenkoppen zoals PHP2Shell, maar is bij zelf ontwikkelde PHP-applicaties een veelvoorkomende bevinding. Frameworks zoals Symfony en Laravel brengen bescherming mee; kwetsbaarheden ontstaan daar vooral waar die voor afzonderlijke routes is uitgeschakeld. Eigen ontwikkelingen zonder framework hebben er vaak helemaal geen.

CSRF in PHP: welke verzoeken beschermd moeten worden

Bescherming hebben alle verzoeken nodig die een toestand wijzigen: opslaan, verwijderen, bestellen, rechten toekennen, instellingen wijzigen, bestanden uploaden, afmelden. Leesaanroepen hebben geen token nodig, zolang ze echt alleen lezen.

Daaruit volgt de eerste regel: Geen wijzigingen via GET. Links zoals loeschen.php?id=… zijn door vreemde pagina's bijzonder gemakkelijk te activeren, bijvoorbeeld via een ingesloten afbeelding. Wijzigende acties horen in POST, PUT, PATCH of DELETE.

De tweede regel: Ook de aanmelding beschermen. Bij de zogeheten login-CSRF wordt een slachtoffer ongemerkt in een account van de aanvaller ingelogd. Voert hij daar betaalgegevens of documenten in, dan komen die bij de aanvaller terecht. Een token in het inlogformulier en een nieuwe sessie-ID na een geslaagde aanmelding voorkomen dat.

Het synchronizer-tokenpatroon in PHP

Voor klassieke applicaties met sessies adviseert OWASP het synchronizer-tokenpatroon: de server genereert een willekeurig token, slaat het op in de sessie en verwacht het in elk wijzigend formulier terug. Belangrijk is dat het token in het formulier of in een eigen header wordt meegestuurd, niet in een 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') ?>">
    <!-- overige velden -->
</form>

hash_equals() vergelijkt in constante tijd, zodat de antwoordtijd niets over de juiste waarde verraadt. Na de login moet u de sessie-ID vernieuwen met session_regenerate_id(true) en een nieuw token genereren. Waarom random_bytes() de juiste bron is en welke functies ongeschikt zijn, legt Veilige willekeurige getallen en tokens in PHP uit.

Eén token per sessie volstaat voor de meeste applicaties. Tokens per formulier of per verzoek verhogen de bescherming iets, maar leiden tot problemen met meerdere geopende tabbladen en de terugknop. Belangrijker dan de frequentie van wisselen is dat het token nooit in URL's opduikt, waar het in logboeken, in de browsergeschiedenis en in referrer-gegevens bij vreemde pagina's terecht kan komen.

Voor stateloze applicaties zonder sessie noemt OWASP als alternatief het ondertekende double-submit-cookie: een token dat via HMAC aan de sessie is gebonden. De eenvoudige, niet-ondertekende variant wordt uitdrukkelijk niet aanbevolen.

Tweede laag: Origin, Sec-Fetch-Site en SameSite

Moderne browsers sturen bij verzoeken informatie over hun herkomst mee. De header Origin noemt de aanroepende pagina, Sec-Fetch-Site zegt of het verzoek van dezelfde site (same-origin), een verwante site (same-site) of een vreemde site (cross-site) afkomstig is. Daaruit is een eenvoudige aanvullende controle op te bouwen:

<?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 benadrukt daarbij twee punten: de herkomst moet volledig worden vergeleken, zodat gelijkend klinkende domeinen er niet doorheen glippen, en een ontbrekende header of de waarde null mag niet als eigen herkomst gelden. Omdat oudere browsers de headers niet altijd sturen, blijft het token de hoofdverdediging.

De derde laag zijn sessiecookies met SameSite. PHP ondersteunt session.cookie_samesite sinds versie 7.3, de standaardwaarde is leeg. Stel minimaal Lax in, voor pure backends is Strict vaak mogelijk. De overige cookie-instellingen beschrijft de gids Sessies in PHP beveiligen.

Gebruikt u een CMS, dan brengt dat meestal eigen hulpmiddelen mee. WordPress werkt bijvoorbeeld met zogeheten nonces, die plug-ins integreren via functies zoals wp_nonce_field() en check_admin_referer(). Kwetsbaarheden ontstaan in de praktijk vooral in uitbreidingen die zulke controles bij afzonderlijke acties vergeten. Controleer daarom vooral zelfgeschreven of weinig gebruikte plug-ins.

Bij interfaces die browsers vanaf andere domeinen gebruiken, beslist bovendien de CORS-configuratie. Geeft de server elke verzoekende herkomst als toegestaan terug en staat hij tegelijk cookies toe, dan schakelt hij de bescherming van de browser zelf uit. OWASP adviseert CORS alleen te openen voor uitdrukkelijk vertrouwde herkomsten; een algemene vrijgave van alle subdomeinen wordt een probleem zodra een daarvan in verkeerde handen valt.

Checklist voor bestaande applicaties

Bij gegroeide projecten helpt een systematische doorlichting:

  1. Alle routes en scripts inventariseren die gegevens wijzigen, ook Ajax-eindpunten en bestandsuploads.
  2. Controleren of daarvoor GET wordt gebruikt en omschakelen naar POST of passende methoden.
  3. Voor elke wijzigende route aantonen dat een token wordt gecontroleerd; uitzonderingen in het framework gericht opsporen.
  4. Login, afmelding en wachtwoordwijziging afzonderlijk testen.
  5. session.cookie_samesite en de herkomstcontrole toevoegen.
  6. XSS-kwetsbaarheden verhelpen, want binnengesmokkelde code kan elk token lezen. De aanpak beschrijft Cross-site scripting in PHP voorkomen.
Belangrijk

Een CSRF-bescherming die alleen in de frontend via JavaScript wordt gecontroleerd, is nutteloos. De controle moet op de server plaatsvinden, voordat de actie wordt uitgevoerd.

Hoe helpt PHP SOS concreet?

  • Wij inventariseren alle wijzigende routes van uw PHP-applicatie en controleren waar een token ontbreekt of is uitgeschakeld.
  • Wij bouwen één centraal controlepunt in, in plaats van tokencontroles over veel scripts te verspreiden.
  • Wij voegen herkomstcontrole en SameSite-instellingen toe, zonder betaal- of loginprocessen te verstoren.
  • Wij testen na de omschakeling opnieuw en documenteren de resultaten.

Een volledige controle van uw applicatie beschrijft de dienst beveiligingsaudit en penetratietest.

Veelgestelde vragen

Is SameSite=Lax genoeg als CSRF-bescherming?

Nee. SameSite-cookies zijn volgens OWASP een extra laag, geen vervanging. Lax verstuurt cookies nog steeds bij gewone paginaaanroepen via GET en aanvallen vanaf subdomeinen van dezelfde website worden niet afgedekt.

Hebben JSON-API's een CSRF-token nodig?

Als de API met cookies authenticeert, ja, of een gelijkwaardige controle. Een alternatief zijn eigen verzoekheaders, die browsers bij vreemde pagina's pas na een CORS-preflight toestaan. Dan mag de CORS-vrijgave echter alleen uitdrukkelijk vertrouwde herkomsten bevatten.

Moet ook het inlogformulier worden beschermd?

Ja. Bij login-CSRF wordt een slachtoffer ongemerkt in een vreemd account ingelogd en voert daar eigen gegevens in. Een token in het inlogformulier en een nieuwe sessie na de aanmelding voorkomen dat.

Beschermt CSRF-bescherming ook tegen XSS?

Andersom: een XSS-kwetsbaarheid schakelt elke CSRF-bescherming uit, omdat binnengesmokkelde code namens de pagina geldige tokens kan lezen en meesturen. Beide zwakke plekken moeten afzonderlijk worden verholpen.

Bronnen

Direct contact

Elk uur telt. Schrijf ons nu.

Beschrijf kort het systeem, de PHP-versie en wat u opmerkt. Wij nemen contact op, brengen de situatie in kaart en werken op afstand tot het incident is afgerond en gedocumenteerd.

Directe hulp