Datasäkerhet: Hur man gör intrång på en sajt (och hur man skyddar sig)

Datasäkerhet

Idag tillhör webbplatser många av våra vardagliga sysslor; vi är inne på mejlen, banken, Facebook, Twitter, olika webbshopar — listan kan göras lång. De allra flesta av dessa webbplatser har någon form av inloggningssystem för att användaren ska kunna stärka sin identitet. Inloggning sker oftast med användarnamn (eller e-postadress) och lösenord.

Många sajter tror sig vara säkra på att ingen obehörig tar sig in på någon annans konto. De har dock glömt bort att skydda sig från den allra vanligaste typen av dataintrång: SQL-injektioner. I den här artikeln kommer jag visa hur man utför en SQL-injektion, men framförallt även hur man skyddar sig mot dem.

Vad är en SQL-injektion?

En SQL-injektion går ut på att man lurar ett system att ta emot och genomföra en del av en databasfråga. I det här fallet rör det sig om en SQL-databas (Structured Query Language), därav namnet. Att det fungerar på vissa system beror på att de som utvecklat webbplatsen har missat att filtrera ut citationstecken och/eller apostrofer, och detta kan man alltså utnyttja genom att bygga ut databasfrågan.

Exempel 1: Utan kryptering

Låt oss säga att vi har ett inloggningssystem som innehåller nedanstående kod.

<?php
   $username = $_POST['username'];
   $password = $_POST['password'];

   $query = 'SELECT * FROM users WHERE username = "' . $username . '" AND password = "' . $password . '"';
?>

Det finns faktiskt fler än ett fel i ovanstående kod. Det allvarligaste felet är att lösenordet inte på något vis är krypterat. Detta innebär att om en hackare skulle komma över databasen skulle denne även kunna se alla användares lösenord, och jag är ganska säker på att du använder ett och samma lösenord på fler än en sajt. Ser du problemet?

Det andra felet är att koden inte filtrerar bort citationstecken och apostrofer. Felet kan dock vara osynligt för vanliga användare som loggar in, i det här fallet är det Lisa som använder det supersäkra lösenordet abc123:

SELECT * FROM users WHERE username = "Lisa" AND password = "abc123";

Men vad händer om vi skulle ange " OR "1" = "1 som lösenord när vi loggar in?

SELECT * FROM users WHERE username = "Lisa" AND password = "" OR "1" = "1";

Hoppsan. Med lika stor sannolikhet som 1 är 1 kommer vi att komma in på Lisas konto.

Om vi inte känner till något användarnamn på sajten så kan vi istället använda oss av " OR "1" = "1 som både användarnamn och lösenord:

SELECT * FROM users WHERE username = "" OR "1" = "1" AND password = "" OR "1" = "1";

Det här kommer istället logga in oss som den första registrerade användaren på sajten, och den första användaren är oftast sajtens administratör. Konsekvenserna av detta kan nog vem som helst räkna ut.

Exempel 2: Med kryptering

Vi ska använda oss av en annan kod som är lite säkrare, men fortfarande inte tillräckligt säker för att stå emot en SQL-injektion. Den här gången är alla lösenord MD5-krypterade. Förutom att vara 22 år gammal är MD5 en så kallad envägskryptering, vilket innebär att den inte kan dekrypteras. För att en användare ska kunna logga in måste man alltså kryptera det inskrivna lösenordet på samma sätt som det lagrade lösenordet och sedan jämföra de krypterade textsträngarna. Oavsett hur lång textsträng man krypterar med MD5 kommer resultatet alltid vara 32 tecken långt.

För att lugna andra insatta webbutvecklare ska jag berätta att MD5 egentligen inte är avsedd för att kryptera lösenord, utan för att ta fram hashsträngar från data för att till exempel kunna jämföra data från två källor och se ifall något har ändrats. För att kryptera lösenord rekommenderar jag det krångligare mcrypt, som jag kommer berätta mer om i en senare artikel. För att göra det lättare både för dig och mig kommer jag dock använda MD5 i detta exempel.

<?php
    $username = $_POST['username'];
    $password = md5($_POST['password']); //Kryptera lösenordet med MD5

    $query = 'SELECT * FROM users WHERE username = "' . $username . '" AND password = "' . $password . '"';
?>

När Lisa ska logga in kommer det lösenord hon skriver in krypteras med MD5 och sedan jämföras med det krypterade lösenordet som ligger lagrat i databasen:

SELECT * FROM users WHERE username = "Lisa" AND password = "e99a18c428cb38d5f260853678922e03";

Om vi skulle försöka logga in med " OR "1" = "1 som lösenord nu skulle det inte fungera, eftersom att textsträngen ändå kommer krypteras innan den skickas till databasen. Därmed har vi gjort systemet lite säkrare på fler än ett sätt.

Användarnamnet är dock fortfarande naket. Det kan vi utnyttja genom att istället ange " OR "1" = "1"; -- som användarnamn och skriva vad som helst som lösenord:

SELECT * FROM users WHERE username = "" OR "1" = "1"; --" AND password = "d41d8cd98f00b204e9800998ecf8427e";

I SQL-språk inleds kommentarer med dubbla bindestreck. Detta innebär att allting efter --ignoreras, och ovanstående kod skulle därför tolkas likadant som nedanstående kod:

SELECT * FROM users WHERE username = "" OR "1" = "1";

Här kommer vi att bli inloggade som den första registrerade användaren igen.

Hur skyddar man sig från SQL-injektioner?

Problemet med all ovanstående kod är som sagt att ingenting filtrerar bort citationstecken och apostrofer. PHP-tillägget MySQLi har dock en inbyggd funktion vid namnreal_escape_string() som tar hand om just detta och lite till. Det den gör är att lägga till ett omvänt snedstreck framför specialtecken så att de “escapas”. Då förstår databasen hur den ska hantera tecknen.

Vår kod som är skyddad från SQL-injektioner ser alltså ut så här:

<?php
    //Anslutningen till databasen
    $db = new mysqli('localhost', 'db_user', 'db_pass', 'database');

    //Gör bara användarnamnet säkert - lösenordet krypteras ändå
    $username = $db->real_escape_string($_POST['username']);
    $password = md5($_POST['password']);

    $query = 'SELECT * FROM users WHERE username = "' . $username . '" AND password = "' . $password . '"';
?>

När någon försöker göra en SQL-injektion nu så kommer databasfrågan bli så här istället:

SELECT * FROM users WHERE username = "\" OR \"1\" = \"1\"; --" AND password = "d41d8cd98f00b204e9800998ecf8427e";

Eftersom att det (troligtvis) inte finns någon användare vid namn " OR "1" = "1"; -- så kommer det bli ett tomt resultat.

Slutord

Oavsett hur snabb, snygg och trevlig en webbplats är så är alltid säkerheten viktigast. Dubbelkolla alla formulär innan du släpper en sajt live så slipper du otrevliga överraskningar.

Bilden i artikeln är tagen av Steven Depolo.

Ivar Johansson

Skriven av

Ivar började med webbutveckling för tio år sedan och har sedan dess utvecklat breda kunskaper inom framför allt webbprestanda, sökmotorer och användarupplevelse. Idag driver han eget företag där han skapar webbplatser som är snabba, säkra och enkla att hantera.

Mejla mig: