Så förhindrar du CSRF-attacker

Cross-Site Request Forgeries (CSRF) är en form av attack, riktad mot webbplatser, som hamnat lite i skuggan av den mer kända metoden Cross-Site Scripting (XSS). Men det kanske den inte borde ha gjort. CSRF-attacker kan både vara svårare att stoppa och göra mer skada än XSS-attacker. Dessutom är de flesta webbsidor sårbara när det kommer till CSRF. Amazon är till exempel det, Digg har likaså varit det precis som Google Adsense. Så om du inte aktivt jobbat med att förhindra CSRF-attacker är din webbtjänst med största sannolikhet också sårbar.

Vad är CSRF?

Man kan säga att CSRF är en form av attack som utnyttjar förtroendet en webbplats har gentemot en användare. Det är alltså lite av motsatsen till vad XSS är, där en användares förtroende till en webbplats istället utnyttjas.

Rent praktiskt fungerar en CSRF-attack som så att en angripare kan få din webbapplikation att utföra funktioner som om det var en anförtrodd (det vill säga inloggad) användare som startade funktionen. Faktum är att själva funktionen startas av den anförtrodda användaren, fast utan dennes vetskap. Angriparen skickar nämligen den förfalskade begäran om att starta funktionen genom att tvinga en anförtrodd användares webbläsare att göra förfrågan.

Jag vet, det här låter ju riktigt avancerat. Men med lite grundkunskaper om hur HTTP fungerar och ett lagom dumt och förenklat exempel är det enklare att förstå.

Låt säga att det finns en webbplats där du som inloggad medlem kan beställa bananer och äpplen. Koden för beställningsformuläret ser ut så här:


<form action="/kop.php" method="post">
<select name="frukt">
    <option name="banan">Bananer</option>
    <option name="applen">Äpplen</option>
</select>
Antal: <input type="text" name="antal" />
<input type="submit" value="Köp!" />
</form>

Och sidan som processar beställningen, skriven i PHP, ser förenklat ut ungefär så här:


if(user_is_loggedin()) {

    buy_fruits($_REQUEST['frukt'], $_REQUEST['antal'], $_SESSION['userid']);

}

Innan beställningen läggs kollar vi alltså att användaren är inloggad, så man kan koppla beställningen till en användare. Säkrare en så kan det väl inte bli — varje gång vet vi att det är en inloggad och godkänd användare som lagt beställningen, eller? Nej, exemplet ovan är så klart skapat för att visa på hur enkelt man kan utföra en CSRF-attack. I det här fallet räcker det med att en person besatt av att beställa tonvis med bananer åt folk infogar följande kod på sin egen webbplats:


<img src="http://www.fruktsajten.se/kop.php?frukt=bananer&antal=10000" />

Om du besöker attackerarens webbplats, där kodraden ovan finns, samtidigt som du är inloggad på fruktbeställningswebbplatsen kommer alltså din webbläsare, utan din vetskap, beställa ett gäng bananer.

Som du kanske förstår så blir sådana här typer av attacker svåra att upptäcka och stoppa. Eftersom själva förfrågan faktiskt kommer från en betrodd användares webbläsare. Dessutom är ju såklart inte CSRF-attacker begränsade till bananbeställning utan praktiskt taget allt som en inloggad användare kan göra skulle också attackeraren kunna utföra — posta kommentarer, sätta betyg, byta lösenord, beställa saker och så vidare.

Speciellt viktigt att skydda sig mot sådana här typer av attacker blir det för webbplatser som erbjuder någon form av mer permanent inloggning, som till exempel Amazon och Flickr har, där användaren kan vara inloggad på en webbplats även om det var en månad sedan han/hon senast besökte tjänsten.

Hur skyddar man sig?

Det är svårt att ge ett hundraprocentigt skydd mot sådana här typer av attacker. Men det finns mycket man kan göra för att minska risken.

Till att börja med bör man se till att man alltid använder POST istället för GET i formulär och Ajax-anrop, så långt det är möjligt. Samt att man i sin kod ser till att det är en POST-förfrågan som kommer och inget annat. Detta ger inte på något sätt ett tillfredsställande skydd, men försvårar det något för den som tänker utföra en attack, och skulle faktiskt vara skydd nog för att förhindra den enkla attacken i exemplet ovan.

Ett bättre skydd mot CSRF-attacker får man genom att försöka säkerställa att användaren använt formuläret i fråga för att skicka förfrågningen till servern. Det kan man göra genom att alltid generera en ny slumpad sträng när sidan laddas, som man sedan både sparar i en session och skickar med i formuläret när det postas. Innan man processar formuläret kollar man sedan att strängen i sessionen överensstämmer med den som kom via postningen av formuläret.

Så om vi återigen tar exemplet ovan och försöker göra det lite mer säkert skulle koden för formuläret kunna se ut så här:1


<?php
// Generera en slumpad sträng
$token = md5(uniqid(rand(), true));
// Spara strängen i sessionen
$_SESSION['token'] = $token;
?>

<form action="/kop.php" method="post">
<select name="frukt">
    <option name="banan">Bananer</option>
    <option name="applen">Äpplen</option>
</select>
Antal: <input type="text" name="antal" />
<input type="hidden" name="token" value="<?= $token ?>" />
<input type="submit" value="Köp!" />
</form>

Och sidan som processar beställningen skulle förenklat kunna se ut som så här:


if(user_is_loggedin()) {

    // Kolla att strängen som finns i sessionen överensstämmer med den som kom via formuläret
    if(isset($_SESSION['token']) && $_SESSION['token'] === $_POST['token']) {

         buy_fruits($_POST['frukt'], $_POST ['antal'], $_SESSION['userid']);

     }

}

Genom att införa dessa ändringar kan vi med större säkerhet veta att förfrågningen kommer från en betrodd användare som använt »rätt« formulär — och på så sätt har vi skyddat oss bättre mot CSRF.

Slutligen: Kan man bli ännu säkrare?

Det finns utan tvivel många saker man kan göra för att förbättra säkerheten ytterligare. Till exempel kan man tidsbegränsa formuläret så att det inte får gå mer än 5 minuter från det att formuläret laddats till dess att postningen av den samma sker. Vilket är vanligt vid inloggning på Internet-banker till exempel. Men vill man vara riktigt säker kan man använda sig av CAPTCHA’s eller ännu bättre: be användaren om lösenordet för att kunna posta formuläret.

Hur som helst. Någon form av skydd bör man implementera varje gång man låter en användare lägga till, ta bort och redigera saker på en webbplats. Vissa saker är dock mer känsliga än andra och kanske behöver ägnas en extra tanke.



  1. Raden som genererar en slumpad sträng är lånad från Chris Shiflett som bland annat skrivit boken Essential PHP Security

6 kommentarer till »Så förhindrar du CSRF-attacker«

Kommentarer

  1. 2 September 2007 kl. 14:12 Jesper

    Tack för artikeln, du förklarar på ett bra sätt hur CSRF-attacker fungerar. Jag hade inte riktigt greppat det förrän nu.

  2. 26 November 2007 kl. 10:46 Pinback

    Hittat under november 2007 - Weeb

  3. 3 April 2008 kl. 0:14 Pimp

    Mycket bra skriven. Dags att lägga upp CSRF-skydd på min webbplats då. Tokens verkar vara det mest vettiga för en användarvänlig webbapplikation.

  4. 6 June 2008 kl. 19:48 sebastian nielsen

    Att kräva att man anger lösenordet igen för ett formulär var en mycket dålig idé. Det är osäkrare att kräva lösenord istället för captcha då lösenord kan snappas upp. Tänk om någon lyckas montera en XSS som plockar värdet ur lösenordsfältet varje sekund?

    Dessutom, är man redan inloggad, så varför ska man behöva logga in igen?

    Captcha är såklart mycket säkrare, iochmed att den ändrar sig varje gång. Dessutom kan man implementera så captchan i sitt initialskede bara är på typ 3 tecken. Varje gång man misslyckas så lägger den till ett tecken, upp till max säg 8 tecken. Så man får en captcha på 3 tecken. Gör man fel, får man en captcha på 4 tecken. Gör man fel igen så får man en captcha på 5 tecken osv till den är på 8 tecken då ökar den inte längre. När man svarar rätt på captchan så återställs failure-räknaren.

    Failure-räknaren kan sparas på användarkontot istället för IP-nummer.

    Kombinera detta med tokens som är låsta till både IP-nummer och användarnamn och det börjar bli riktigt säkert.

  5. 9 June 2008 kl. 8:22 Niklas

    Sebastian: Din idé om captchan med dynamisk längd är lite intressant.

    Om man pratar om rena CSRF-attacker så skulle jag säga att lösenord och captcha skyddar ungefär lika mycket. Men om man som du säger hamnar i en situation där lösenordet har fiskats fram på något sätt så kan ju en captcha skydda något mer, i den bemärkelse att det blir svårare att rent programkodsmässigt skriva en kod som automatiskt utför saker.

    Men det finns ju också situationer då en fråga om lösenord kan skydda mycket mer än en captcha. Till exempel när någon kommit över en annans användares konto, utan att veta om lösenordet. Det är ju relativt vanligt att man begär lösenord igen för mer radikala förändringar som inte utförs så ofta, så som att ändra ett lösenord eller radera ett konto. Just för att säkerställa att ingen annan lyckats komma åt ett konto från en tidigare användare av datorn som varit slarvig och inte loggat ut.

    Slutligen; på vilket sätt menar du att en token som är låst till IP-nummer/användarnamn skulle vara mer säker än en slumpvist genrererad? Kan inte riktigt se att det skulle bli ett bättre skydd mot CSRF.

  6. 22 August 2010 kl. 1:32 scandi

    Thehttp://www.scandi-tours.com - . details underneath dissemble routes to Denmark, Norway, Sweden and Finland from the UK and Ireland. In her own coin representing connections within Scandinavia itself, discern the “Getting There” sections in restitution with a view each Individualistic country. NEXT TO MATTE Flying to Scandinavia isn’t predominantly cheap. Lowcost come down with flights do exhale, but they’re not teeming and considerably between and gratify at worst scant destinations; more Bumf follows, and is at in unison’s fingertips from the operators listed guardianship “Packages”. Continually the most filch startingpoint on account of the cheapest deals to Scandinavia is the classified sections in the Sunday newspapers

Lämna en kommentar

Du kan följa kommentarerna till inlägget via RSS. Radbrytningar och paragrafer skapas automatiskt. Viss HTML är tillåten: <a href="">   <em>   <strong>.

Bloggarkivet