Säkerhetstänkande i webbapplikationer

När man specar en webbapplikation är det sällan som säkerhetsaspekterna tas med explicit. Det kan lätt betraktas som ”Någon Annans Problem”: systemadministratören ska se till att brandväggen är på plats, IT-ledningen bestämmer om lösenordspolicy etc. Men i slutänden är inget av detta relevant om applikationen som körs är full av hål, och då är det svårt att skylla ifrån sig. Man måste istället vara påläst om vilka attacker som ofta förekommer och ta aktiva åtgärder för att stoppa dem.

Jag tänkte att vi skulle ta en titt på de vanligaste hålen i webbapplikationer, och hur man kan göra för att täppa till dem.

SQL Injection

Om koden använder sig av icke-validerade strängar för att bygga upp en SQL-fråga så är du blottad för denna typ av attack. Det syns lätt med lite förenklad Java-kod:

boolean loginUser(Connection dbConnection,
                  String username, 
                  String password) throws Exception {

    String sql = "select * from User where username='"
               + username + "' and password='" + password + "'";
    Statement stmt = dbConnection.createStatement();
    try {
        return stmt.executeQuery(sql).next();
    } finally {
        stmt.close();
    }
}

Metoden ovan ska alltså returnera ”true” om det finns en rad med matchande username och password, annars ”false”. Enkelt. Men om strängarna ”username” och ”password” kommer direkt från ett webbformulär (eller på annat sätt som vi inte kan ha full kontroll på) så får vi problem. Anta till exempel att användaren matar in ”kalle” som username och ”‘ or true;–” som password… Databasen kommer då att ignorera lösenordskollen helt och hållet.

Hackaren kan naturligtvis göra mycket elakare saker än så, om det vill sig illa. Anta att han skriver in, som lösenord, ”xx’; delete from User;–”. Plötsligt blir det svårt att logga in. För alla…

Lösningen på detta problem är förstås att sanitetskontrollera indata. Enklast är att använda API-anrop mot databasen som tar parametrar istället för en komplett query. I Java med JDBC innebär det att du alltid bör använda dig av PreparedStatement istället för en vanlig Statement. I exemplet ovan blir det istället så här:

boolean loginUser(Connection dbConnection,
                  String username, 
                  String password) throws Exception {

    PreparedStatement stmt = dbConnection.prepareStatement(
                    "select * from User where username=? and password=?");
    stmt.setString(1, username);
    stmt.setString(2, password);
    try {
        return stmt.executeQuery().next();
    } finally {
        stmt.close();
    }
}

JDBC tar då hand om att filtrera ut styrtecken som gör SQL Injection möjlig.

Glöm inte att SQL Injection (eller motsvarande) kan vara ett problem på andra ställen du tillåter databas-APIer att styras av icke-kontrollerad indata. Du blir t.ex. inte säker från SQL Injection bara för att du använder dig av Hibernate som databasramverk. Om du bygger upp dina HQL-stängar med icke-kontrollerad indata så kan du få precis samma effekt därifrån.

Cross-site Request Forgery (CSRF)

Klicka på en knapp på min sida så stjäl jag din email… Låter rätt illa, men kan lätt bli verkligt om din webb-mail-applikation är dåligt skriven. Ett hypotetiskt fall (som inte fungerar… längre…) är detta: antag att Google Mail med en enkel formulärpostning tillåter inloggade användare att skapa email-filter. Om jag nu har en elak webbsida som innehåller ett färdig-ifyllt formulär där jag lägger till ett filter som skickar kopior på alla dina mail till en adress som jag äger. Du loggar in på GMail som vanligt (väldigt många har alltid en GMail-flik uppe i sin browser) och hamnar senare på min sida. Jag lockar dig att klicka på min formulär-knapp som postar det färdiga formuläret till GMail och lägger till filtret till den aktuella användaren, dvs DU.

Om Javascript hade tillåtits att posta formulär till andra sajter än den som skriptet ligger på hade jag inte ens behövt lura dig att klicka på knappen, men de flesta (alla?) browsrar stoppar Javascript från att göra detta (och tur är väl det!).

Ett sätt att täta detta hål är att se till att det postade formuläret genererats från samma session som det som postar det. Det kan man göra genom att generera en unik engångskod som sparas i sessionen, och dessutom tas med som en dold parameter i formuläret. När servern kan validera att dessa värden är identiska så betraktas postningen som giltig — annars inte.

Cross-site Scripting (XSS)

I takt med att allt fler blogg-sajter och ”sociala nätverk” dyker upp så blir det allt vanligare att användare kan anpassa ”sin sida” med egna mallar, egen html eller liknande. Här kan vi få ett liknande hot som med CSRF, men en farligare variant, och som inte går att stoppa med samma metod.

Attacken kan se ut på liknande sätt: en elak användare lägger upp ett dolt formulär på ”sin sida”, som han fått in genom att ändra sin layout-mall eller liknande. När offret tittar på den elaka sidan postas formuläret och kan göra samma saker som den inloggade användaren får göra. Problemet förstoras av att den elaka koden ligger på samma domän som offrets session är inloggad i. Det medför att eventuella javascript som ligger på den elaka sidan kan få fritt spelrum. Offret behöver i det här fallet alltså inte ens manuellt klicka på en knapp — det räcker att sidan visas så att javascripten får möjlighet att köras!

Man kan inte använda samma försvar som i CSRF-fallet eftersom ett elakt javascript också kan generera ett formulär, plocka fram den unika sessionsnyckeln, och sedan använda det i sin elaka formulärpostning.

Det bästa sättet att klara sig mot den här typen av attacker är att antingen inte tillåta någon egen kod alls för användare, eller lägga mycket möda på att se till att den koden som läggs in är absolut ren från javascript eller andra potentiellt farliga styrkoder. Det är dock inte så enkelt som man skulle kunna tro…

En av de bäst dokumenterade riktiga attackerna med denna metod gjordes av ”Samy” på MySpace. Läs hans egna spännande beskrivning här! Notera att MySpace mycket riktigt hade lagt in flera spärrar för att undvika sådana här problem, men att samy hittade vägar förbi var och en av dem.

Avslutningsvis

Det finns naturligtvis många fler sätt att attackera en webbapplikation. Det här är bara ett smakprov på några av de mest kända, men som trots detta bevisligen fungerar överraskande ofta — även på helt nyutvecklade webbtjänster. Det måste tyda på att webbutvecklare ändå inte tar till sig de kunskaper om risker och tekniker som finns där ute, och som hackare inte drar sig för att använda!

Länkar

Open Web Application Security Project (OWASP) är en stor källa till information om säkerhetsaspekter i webbapplikationer, och har även mer information om de tre typer av attacker som jag beskrivit ovan:

Missa inte heller seriestrippen om praktiskt tillämpning av SQL Injection: historien om lille Bobby Tables!