LabbCybersäkerhet · Etisk hackning
0/0
Laboration

Injektion — grundorsak och parametriserade frågor

Mål: Förstå varför injektionssårbarheter uppstår, och kunna skriva kod som strukturellt omöjliggör dem.

Observera

Genomför PortSwigger Web Security Academys modul om SQL injection, eller DVWA:s SQL Injection-övning på nivå "Low". Båda är guidade och har facit. Notera vad som händer med applikationens svar när indata inte behandlas som enbart data.

Höj sedan DVWA:s säkerhetsnivå till "High" och observera att samma tillvägagångssätt inte längre fungerar. Skillnaden mellan nivåerna är hela lärdomen.

Analysera grundorsaken

Använd DVWA:s inbyggda "View Source"-knapp och jämför koden mellan säkerhetsnivåerna. Detta är det mest lärorika momentet i hela laborationen.

Grundorsaken är alltid densamma: applikationen bygger ett kommando genom att klistra ihop kod och användardata till en enda sträng. Databasen får sedan en färdig sträng och har ingen möjlighet att veta vilken del som var avsedd som kommando och vilken som var användarens data.

// Grundorsaken: indata blir en del av kommandots struktur
$query = "SELECT * FROM users WHERE id = '" . $_GET['id'] . "'";
Vad koden gör — förklaring rad för rad

Den sårbara raden, i PHP. Den bygger en databasfråga genom att klistra ihop text. Det är precis det som orsakar injektionssårbarheter.

$query
I PHP börjar variabelnamn med dollartecken. En variabel är en namngiven låda som håller ett värde.
"SELECT * FROM users WHERE id = '"
En databasfråga skriven som text. SELECT * betyder "hämta alla kolumner", FROM users "ur tabellen users", WHERE id = "där id är lika med".
.
Punkten mellan textbitarna är PHP:s sätt att klistra ihop text. Här ligger hela problemet.
$_GET['id']
Värdet besökaren skickade med i webbadressen. Det klistras rakt in i frågan, och databasen kan sedan inte skilja frågans struktur från besökarens text.

Samma mönster återkommer i andra former — OS-kommandon som byggs ihop som strängar, LDAP-filter, filsökvägar. Lär dig känna igen strängkonkatenering till ett kommando som varningssignal, oavsett teknik.

Bygg försvaret

1. Parametriserade frågor (prepared statements). Här skickas kommandots struktur och datan separat. Databasen vet då alltid vad som är vad, och användardata kan per definition inte ändra frågans struktur.

PHP med PDO:

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
$user = $stmt->fetch();
Vad koden gör — förklaring rad för rad

Den säkra versionen i PHP. I stället för att klistra ihop skickas frågan och värdet var för sig, i två steg. Databasen vet då alltid vad som är kommando och vad som är data.

$pdo->prepare(...)
Steg ett: skicka frågans form till databasen, utan värdet. PDO är PHP:s standardsätt att prata med databaser.
?
Frågetecknet är en platshållare — inte ett skrivfel. Det betyder "här kommer ett värde senare".
$stmt->execute([$_GET['id']])
Steg två: skicka värdet. Databasen vet redan hur frågan ser ut, så värdet kan aldrig ändra dess betydelse.
$stmt->fetch()
Hämtar ut resultatraden så att programmet kan använda den.

Python med psycopg:

cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
Vad koden gör — förklaring rad för rad

Samma skydd i Python. Formen skiljer sig från PHP men principen är identisk: frågan och värdet skickas som två separata saker.

cur.execute(
cur står för cursor, programmets handtag mot databasen. execute kör frågan.
%s
Pythons platshållare, motsvarigheten till PHP:s frågetecken. Den ska inte omges av citattecken.
(user_id,)
Värdena skickas som ett andra, separat argument. Kommat efter user_id är Pythons sätt att markera att detta är en lista med ett enda element.

Node.js med mysql2:

const [rows] = await conn.execute(
  'SELECT * FROM users WHERE id = ?',
  [userId]
);
Vad koden gör — förklaring rad för rad

Samma sak igen, nu i JavaScript på serversidan (Node.js). Tredje språket, samma princip — det är avsiktligt, så att du känner igen mönstret oavsett teknik.

const
Deklarerar ett värde som inte ska ändras efteråt.
await
Vänta tills databasen svarat innan programmet går vidare. Databasanrop tar tid, och utan await skulle programmet rusa förbi.
?
Platshållaren, precis som i PHP.
[userId]
Värdena i en egen lista, separat från frågan.
const [rows] =
Plockar ut den första delen av databasens svar och kallar den rows.
Viktigt: execute("... WHERE id = " + user_id) är fortfarande sårbart även om du använder ett bibliotek som stöder parametrar. Det är parameteranvändningen som skyddar, inte biblioteket i sig.

2. ORM med parametrisering som standard. Django ORM, SQLAlchemy, Eloquent och Prisma parametriserar automatiskt. Undantaget är råa frågor (raw(), DB::raw()), som måste hanteras manuellt.

3. Minsta möjliga behörighet i databasen. Applikationskontot ska inte vara root. Ett konto som bara har SELECT, INSERT och UPDATE på de tabeller det behöver begränsar skadan kraftigt om något ändå går fel:

CREATE USER 'webapp'@'localhost' IDENTIFIED BY '<starkt lösenord>';
GRANT SELECT, INSERT, UPDATE ON shop.orders TO 'webapp'@'localhost';
-- Medvetet INTE: DROP, ALTER, GRANT, eller åtkomst till andra databaser
Vad koden gör — förklaring rad för rad

SQL-kommandon som körs i databasen, inte i applikationen. De skapar ett konto åt webbapplikationen och ger det bara de rättigheter det faktiskt behöver.

CREATE USER 'webapp'@'localhost'
Skapar ett databaskonto som heter webapp och bara får anslutas från samma maskin.
GRANT SELECT, INSERT, UPDATE
Ger rätt att läsa, lägga till och ändra rader — men ingenting mer.
ON shop.orders
Rättigheterna gäller bara tabellen orders i databasen shop, inte något annat.
-- Medvetet INTE: DROP, ALTER...
Två bindestreck inleder en kommentar i SQL. Raden påminner om vad som avsiktligt utelämnats: rätten att radera tabeller eller dela ut rättigheter vidare.

4. Validering som komplement, inte som huvudförsvar. Validera att ett ID är ett heltal och att en e-postadress ser ut som en e-postadress. Men förlita dig aldrig på svartlistning av tecken som enda skydd — det går nästan alltid att kringgå. Parametrisering är huvudförsvaret, validering är ett extra lager.

Verifiera

Kör om samma övning från Del A mot din åtgärdade kod. Bekräfta dessutom att den normala funktionaliteten fortfarande fungerar — en vanlig miss är att en "fix" också sönder den legitima användningen. Testa gärna med indata som innehåller apostrof (t.ex. efternamnet O'Brien), som ska fungera felfritt i en korrekt parametriserad fråga.

Dokumentera

Skriv fyndet med allvarlighetsgrad enligt CVSS, kodreferens (fil och radnummer), och en konkret rekommendation med kodexempel. Lägg till en notering om huruvida sårbarheten var nåbar utan inloggning, vilket påverkar allvarlighetsgraden.

Reflektionsfrågor: Varför är parametrisering ett strukturellt försvar medan teckenfiltrering är ett ytligt? Vilka andra ställen i en applikation bygger kommandon av strängar?


Kunskapskontroll

Välj ett svar per fråga. Du får återkoppling direkt, och avsnittet markeras som klart när alla frågor är besvarade.

1Vad är grundorsaken till en injektionssårbarhet?

2Varför skyddar en parametriserad fråga?

3Du använder ett bibliotek som stöder parametrar, men bygger frågesträngen med konkatenering ändå. Vad gäller?