SQL Injection (SQLi): Sådan fungerer databasens værste fjende

Selvom SQL Injection (SQLi) har eksisteret i årtier, troner den stadig højt på OWASP Top 10 over de mest kritiske web-sårbarheder år efter år. Men hvad er en SQL-injektion egentlig, hvordan udnytter hackere det, og hvordan kan man som udvikler skrive kode, der er 100% immun?

I denne artikel dykker vi ned i maskinrummet på en usikker webapplikation og piller en af web-sikkerhedens mest klassiske sårbarheder fra hinanden.


1. Baggrunden: Hvad er SQL?

For at forstå injektionen, må man forstå fundamentet. Database Management Systems (DBMS) som MySQL, PostgreSQL og Microsoft SQL Server benytter sproget SQL (Structured Query Language) til at oprette, læse, slette og opdatere data.

Når du som bruger logger ind på en hjemmeside, tager applikationens backend dit brugernavn og password og bygger dynamisk en SQL-streng, som sendes til databasen. Den typiske login-forespørgsel kan se således ud i backend-koden (Her i fiktiv PHP):

$username = $_POST['username'];
$password = $_POST['password'];

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

Hvis du taster admin og MinKode123, bygger programmet følgende forespørgsel til databasen:

SELECT * FROM users WHERE username = 'admin' AND password = 'MinKode123'

Databasen slår op, og hvis betingelsen er opfyldt (TRUE), logges brugeren ind. Alt er godt.


2. Angrebet: Når Data Bliver Til Kode

Problemet med ovenstående kode er, at den er dybt naiv! Udvikleren stoler blindt på, at brugeren kun taster et legitimt brugernavn ind i feltet.

Backend’en tager bare brugerens rå data og klistrer den direkte sammen med databasens kodestruktur ved hjælp af tekstsammenkædning (concatenation).

Hvad sker der, hvis en angriber i brugernavnsfeltet i stedet for admin indtaster følgende streng?

admin' OR '1'='1

Lad os sætte det ind i udviklerens skrøbelige backend-script igen:

SELECT * FROM users WHERE username = 'admin' OR '1'='1' AND password = ''

Bum! Sådan opstår magien:

  1. Angriberen brugte sit eget anførselstegn (') til tidligt at afslutte programmørens tiltænkte datavariabel.
  2. Dernæst injicerede hackeren sin egen ondsindede SQL-logik: OR '1'='1'.
  3. Databasen læser forespørgslen fra venstre til højre: “Find en bruger der hedder admin” ELLER “Hvor 1 er lig med 1”.
  4. Da tallet 1 altid er lig med 1 (matematisk sandhed), bliver hele SQL-klausulen automatisk uimodsigeligt sand (TRUE), uanset hvad passwordet er sat til.

Angriberen logges nu ind, typisk som den absolut første bruger i tabellen (som normalt er administrator), uden overhovedet at kende adgangskoden!

Typer af SQL Injection

  • In-band SQLi (Klassisk): Data udtrækkes og vises direkte på hjemmesiden (F.eks ved at smide koden UNION SELECT password FROM users i et søgefelt).
  • Blind SQLi (Boolean/Time-Based): Siden viser intet brugbart output (som f.eks. “SQL Syntax Error”), men ved at stille databasen Ja/Nej spørgsmål – eller dømme udfra hvor mange sekunder databasen er om at svare (Time-Delays som sleep(10)) – kan angriberen langsomt dumpe hele tabeller bogstav for bogstav.

3. Konsekvenser af et vellykket SQLi-angreb

Hvis applikationen er sårbar, giver det angriberen frit spil direkte i databasen:

  • Bypass Authentication: Som demonstreret kan loging-sider forbigås fuldstændig.
  • Data Eksfiltrering: Hackeren kan stjæle forretningshemmeligheder, kreditkort, GDPR-data og passwords via skræddersyede SELECT anmodninger.
  • Manipulation af Integritet: Data kan rettes, slettes eller “droppes” totalt (sletning af hele databasen).
  • Remote Code Execution (RCE): I værste fald kan backend-databasen udnyttes til at køre reelle kommandoprompt scripts (f.eks. xp_cmdshell i MSSQL) direkte på operativsystemet og overtage serveren.

4. Mitigering: Hvordan beskytter vi os? (Forsvaret)

Man skulle tro, at en sårbarhed fra internettets spæde ungdom ville være udryddet, men den lever videre primært grundet dovenskab og “quick-fixes” i kode. CURE’en er heldigvis meget enkel:

Løsning 1: Prepared Statements (Parameterized Queries)

Dette er Guldstandarden og reelt det eneste korrekte modsvar i moderne udvikling!

I stedet for at klistre rå variabler direkte sammen i en tekststreng, sender applikationen først strukturen til databasen for sig, og derefter dataen adskilt.

// PDO Prepared Statement i PHP
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :bruger AND password = :kode');
$stmt->execute(['bruger' => $username, 'kode' => $password]);

Hvorfor virker dette altid? Når man benytter Prepared Statements behandles input udelukkende som et bogstaveligt Data-parameter - aldrig som operativ kode. Hvis hackeren igen prøver overstående trick og taster admin' OR '1'='1, vil databasen bogstavelig talt lede efter en bruger i systemet hvis fysiske fornavn faktisk staves “admin' OR '1'='1” (!), og dermed afvise login-forsøget.

Løsning 2: Brug af ORM (Object-Relational Mapping)

Brug af persistens-frameworks (som Entity Framework i C#, Hibernate i Java eller Eloquent i Laravel) fjerner ofte problemet ved slet ikke at lade udvikleren skrive RAW SQL, men lade fremeworket generere det bagvedliggende SQL automatisk ved hjælp af skudsikre Prepared Statements by design.

Supplerende Sikkerhed (Defense in Depth)

  • Input Validering & Sanitering: Fjernelse af farlige tegn hjælper, men man kan let glemme et escape-tegn. Det bør aldrig stå alene som forsvar. Du skal validere input (“Er dette kun tal?”) men altid anvende preparedness!
  • Principle of Least Privilege: Selve databasens superbruger/admin-konto (sa eller root) skal aldrig bruges af webapplikationen til dens forespørgsler. Giv applikationen sin egen database-bruger, der kun har adgang til de specifikke tabeller, webstedet reelt har behov for at læse fra. Selv ved en injektion vil hackerens skadeomfang på databasen derved være minimeret.

Kilder

> Quiz: Test din viden

1. Hvad er SQL Injection?

2. Hvad gør payload'en ' OR '1'='1 i et login-felt?

3. Hvad er løsningen mod SQL Injection?

4. Hvad kan en vellykket SQL Injection give adgang til?