Lyhyesti: CSRF:n estätte PHP:ssä vaatimalla, että jokaisen pyynnön, joka muuttaa jotakin, mukana tulee salainen, sessioon sidottu token, jonka tarkistatte palvelimella funktiolla hash_equals(). Lisäksi tarkistatte otsikot Origin tai Sec-Fetch-Site ja asetatte sessioevästeille SameSite. GET-pyynnöt eivät saa koskaan muuttaa tietoja, ja myös kirjautumislomake tarvitsee suojan.
Napsautus, jota kukaan ei huomaa
Työntekijä on kirjautunut verkkokaupan hallintaan ja avaa toisessa välilehdessä vieraan sivun. Tämä sivu lähettää taustalla lomakkeen hallintaan, esimerkiksi sähköpostiosoitteen muuttamiseksi tai tilin luomiseksi. Selain liittää sessioevästeen automaattisesti. Sovellukselle pyyntö näyttää samalta kuin mikä tahansa oman tiimin pyyntö.
Tämä on Cross-Site Request Forgery, lyhyesti CSRF: pyyntö, jonka kirjautunut käyttäjä tietämättään lähettää sovellukselle, johon hän luottaa. Hyökkääjä ei näe vastausta, mutta ei tarvitsekaan. Riittää, että toiminto suoritetaan.
CSRF ei ole otsikoissa kuten PHP2Shell, mutta se on itse kehitetyissä PHP-sovelluksissa yleinen havainto. Kehykset kuten Symfony ja Laravel tuovat mukanaan suojan; aukkoja syntyy siellä lähinnä, missä se on kytketty pois yksittäisiltä reiteiltä. Ilman kehystä tehdyillä omilla toteutuksilla sitä ei usein ole lainkaan.
CSRF PHP:ssä: mitkä pyynnöt on suojattava
Suojaa tarvitsevat kaikki pyynnöt, jotka muuttavat tilaa: tallennus, poisto, tilaus, oikeuksien antaminen, asetusten muutos, tiedostojen lataus, uloskirjautuminen. Lukevat kutsut eivät tarvitse tokenia, kunhan ne todella vain lukevat.
Tästä seuraa ensimmäinen sääntö: ei muutoksia GET:llä. Linkit kuten loeschen.php?id=… voidaan laukaista vierailta sivuilta erityisen helposti, esimerkiksi upotetun kuvan kautta. Muuttavat toiminnot kuuluvat menetelmiin POST, PUT, PATCH tai DELETE.
Toinen sääntö: suojatkaa myös kirjautuminen. Niin sanotussa kirjautumisen CSRF:ssä uhri kirjataan huomaamatta hyökkääjän tilille. Jos hän syöttää sinne maksutietoja tai asiakirjoja, ne päätyvät hyökkääjälle. Token kirjautumislomakkeessa ja uusi sessiotunnus onnistuneen kirjautumisen jälkeen estävät tämän.
Synkronointitoken-malli PHP:ssä
Perinteisille sessioita käyttäville sovelluksille OWASP suosittelee synkronointitoken-mallia (Synchronizer Token): palvelin luo satunnaisen tokenin, tallentaa sen sessioon ja odottaa sen palautuvan jokaisessa muuttavassa lomakkeessa. Tärkeää on, että token lähetetään lomakkeessa tai omassa otsikossa, ei evästeessä.
<?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') ?>">
<!-- lisäkentät -->
</form>
hash_equals() vertaa vakioajassa, joten vasteaika ei paljasta oikeaa arvoa. Kirjautumisen jälkeen teidän tulisi uusia sessiotunnus funktiolla session_regenerate_id(true) ja luoda uusi token. Miksi random_bytes() on oikea lähde ja mitkä funktiot ovat sopimattomia, selittää Turvalliset satunnaisluvut ja tokenit PHP:ssä.
Yksi token sessiota kohden riittää useimmille sovelluksille. Lomake- tai pyyntökohtaiset tokenit lisäävät suojaa hieman, mutta aiheuttavat ongelmia, kun useita välilehtiä on auki ja takaisin-painiketta käytetään. Vaihtotiheyttä tärkeämpää on, ettei token koskaan esiinny URL-osoitteissa, joissa se voi päätyä lokeihin, selainhistoriaan ja Referrer-tietoina vieraille sivuille.
Tilattomille sovelluksille ilman sessiota OWASP mainitsee vaihtoehtona allekirjoitetun Double-Submit-Cookien: tokenin, joka on sidottu istuntoon HMAC:lla. Yksinkertaista, allekirjoittamatonta muunnelmaa ei nimenomaisesti suositella.
Toinen kerros: Origin, Sec-Fetch-Site ja SameSite
Nykyaikaiset selaimet lähettävät pyynnöissä tietoa alkuperästään. Otsikko Origin nimeää kutsuvan sivun, Sec-Fetch-Site kertoo, onko pyyntö samalta sivulta (same-origin), sukulaiselta (same-site) vai vieraalta (cross-site). Siitä voi rakentaa yksinkertaisen lisätarkistuksen:
<?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 korostaa tässä kahta kohtaa: alkuperä on verrattava kokonaan, jotta samannäköiset verkkotunnukset eivät pääse läpi, ja puuttuvaa otsikkoa tai arvoa null ei saa pitää omana alkuperänä. Koska vanhemmat selaimet eivät aina lähetä otsikoita, token pysyy pääpuolustuksena.
Kolmas kerros ovat sessioevästeet asetuksella SameSite. PHP tukee asetusta session.cookie_samesite versiosta 7.3 alkaen, oletusarvo on tyhjä. Asettakaa vähintään Lax, pelkille hallintaosille Strict on usein mahdollinen. Muut evästeasetukset kuvaa opas Sessioiden suojaaminen PHP:ssä.
Jos käytätte CMS:ää, siinä on yleensä omat työkalunsa. WordPress käyttää esimerkiksi niin sanottuja noncejä, jotka laajennukset ottavat käyttöön funktioilla kuten wp_nonce_field() ja check_admin_referer(). Aukkoja syntyy käytännössä lähinnä laajennuksissa, jotka unohtavat tällaiset tarkistukset yksittäisistä toiminnoista. Tarkistakaa siksi erityisesti itse kirjoitetut tai vähän levinneet laajennukset.
Rajapinnoissa, joita selaimet käyttävät muilta verkkotunnuksilta, ratkaisee lisäksi CORS-konfiguraatio. Jos palvelin palauttaa jokaisen pyytävän alkuperän sallittuna ja sallii samalla evästeet, se kumoaa itse selaimen suojan. OWASP suosittelee avaamaan CORSin vain nimenomaisesti luotetuille alkuperille; kaikkien aliverkkotunnusten yleissallinta muuttuu ongelmaksi heti, kun yksi niistä joutuu vieraisiin käsiin.
Tarkistuslista olemassa oleville sovelluksille
Vuosien mittaan kasvaneissa projekteissa auttaa järjestelmällinen läpikäynti:
- Kartoittakaa kaikki reitit ja skriptit, jotka muuttavat tietoja, myös Ajax-päätepisteet ja tiedostolataukset.
- Tarkistakaa, käytetäänkö niihin GET:iä, ja siirtäkää ne POSTiin tai sopiviin menetelmiin.
- Osoittakaa jokaiselle muuttavalle reitille, että token tarkistetaan; etsikää kehyksen poikkeukset erikseen.
- Testatkaa kirjautuminen, uloskirjautuminen ja salasanan vaihto erikseen.
- Lisätkää
session.cookie_samesiteja alkuperän tarkistus. - Korjatkaa XSS-aukot, sillä ujutettu koodi voi lukea jokaisen tokenin. Toimintatavan kuvaa Cross-Site-Scripting-hyökkäysten estäminen PHP:ssä.
CSRF-suoja, joka tarkistetaan vain käyttöliittymässä JavaScriptillä, on tehoton. Tarkistuksen on tapahduttava palvelimella ennen toiminnon suorittamista.
Miten PHP SOS auttaa konkreettisesti?
- Kartoitamme PHP-sovelluksenne kaikki muuttavat reitit ja tarkistamme, missä token puuttuu tai on kytketty pois.
- Rakennamme keskitetyn tarkistuspisteen sen sijaan, että tokenien tarkistukset hajotettaisiin moniin skripteihin.
- Lisäämme alkuperän tarkistuksen ja SameSite-asetukset häiritsemättä maksu- tai kirjautumisprosesseja.
- Testaamme muutoksen jälkeen uudelleen ja dokumentoimme tulokset.
Sovelluksenne täydellisen tarkistuksen kuvaa palvelu tietoturva-auditointi ja tunkeutumistesti.
Usein kysytyt kysymykset
Riittääkö SameSite=Lax CSRF-suojaksi?
Ei. SameSite-evästeet ovat OWASPin mukaan lisäkerros, eivät korvaaja. Lax lähettää evästeet yhä tavallisissa sivukutsuissa GET:llä, eikä saman verkkosivuston aliverkkotunnuksilta tulevia hyökkäyksiä kateta.
Tarvitsevatko JSON-rajapinnat CSRF-tokenin?
Jos rajapinta todentaa evästeillä, kyllä tai vastaavan tarkistuksen. Vaihtoehto ovat omat pyyntöotsikot, jotka selaimet sallivat vieraille sivuille vasta CORS-esitarkistuksen jälkeen. Silloin CORS-sallinnan saa kuitenkin antaa vain nimenomaisesti luotetuille alkuperille.
Pitääkö myös kirjautumislomake suojata?
Kyllä. Kirjautumisen CSRF:ssä uhri kirjataan huomaamatta vieraalle tilille, ja hän syöttää sinne omia tietojaan. Token kirjautumislomakkeessa ja uusi sessio kirjautumisen jälkeen estävät tämän.
Suojaako CSRF-suoja myös XSS:ltä?
Päinvastoin: XSS-aukko kumoaa jokaisen CSRF-suojan, koska ujutettu koodi voi sivun nimissä lukea kelvollisia tokeneita ja lähettää ne mukana. Molemmat haavoittuvuudet on korjattava erikseen.