SQL Injection Prevention
SQL injection er en af de mest kritiske sikkerhedstrusler, hvor angribere kan manipulere database-forespørgsler ved at indsætte ondsindet SQL-kode.
Om Truslen
SQL injection opstår når uvalideret brugerinput inkluderes direkte i SQL-forespørgsler, hvilket giver angribere mulighed for at modificere eller udføre arbitrære database-kommandoer. Ifølge OWASP Top 10 er injection-angreb konsekvent blandt de mest kritiske sikkerhedstrusler. En succesfuld SQL injection kan resultere i komplet database-kompromittering, herunder datatyveri, datamanipulation, adgangskontrol-bypass og i nogle tilfælde remote code execution på databaseserveren. Studier viser at over 65% af webapplikationer er sårbare over for SQL injection i en eller anden form. Konsekvenserne kan være katastrofale for både virksomheder og brugere, med potentielt tab af følsomme data, økonomiske tab og betydelig reputationsskade.
Key Points
- ✓Brug altid prepared statements med parameteriserede queries
- ✓Brug aldrig streng-konkatenation til at bygge SQL-queries med brugerinput
- ✓Implementer whitelist input-validering for alle brugerinput
- ✓Brug ORM (Object-Relational Mapping) frameworks der håndterer parameterisering automatisk
- ✓Begræns database-brugerrettigheder til minimum nødvendigt (principle of least privilege)
- ✓Undgå at vise detaljerede database-fejlmeddelelser til slutbrugere
- ✓Implementer Web Application Firewall (WAF) til at detektere SQL injection-forsøg
- ✓Escape special characters hvis prepared statements ikke er mulige (sidste udvej)
- ✓Udfør regelmæssige sikkerhedsaudits og penetration testing
- ✓Brug stored procedures som et ekstra lag af beskyttelse (men ikke som primær forsvar)
- ✓Implementer input-længdebegrænsninger for at reducere attack surface
- ✓Log og monitorer unormale database-forespørgsler
- ✓Opdater regelmæssigt database-software og PHP til nyeste sikkerhedspatches
- ✓Brug separate database-konti for forskellige applikationsniveauer
Sårbar Kode (UNDGÅ)
Brug ALDRIG denne kode i produktion!
<?php
// SÅRBAR KODE - Brug ALDRIG dette i produktion!
// Direkte streng-konkatenation med brugerinput
$username = $_POST['username'];
$password = $_POST['password'];
// Ekstrem sårbarhed: Ingen sanitering eller escaping
$query = "SELECT * FROM users WHERE username = '" . $username . "' AND password = '" . $password . "'";
$result = mysqli_query($conn, $query);
if (mysqli_num_rows($result) > 0) {
echo "Login successful!";
// Attacker kan bruge: ' OR '1'='1 som username
// Resulterende query: SELECT * FROM users WHERE username = '' OR '1'='1' AND password = ''
// Dette vil altid returnere true og give adgang
} else {
echo "Login failed!";
}
// Endnu et sårbart eksempel: Søgefunktion
$search = $_GET['search'];
$query = "SELECT * FROM products WHERE name LIKE '%$search%'";
$result = mysqli_query($conn, $query);
while ($row = mysqli_fetch_assoc($result)) {
echo $row['name'] . "<br>";
}
// Attacker kan bruge: %' UNION SELECT username, password FROM users--
// Dette vil afsløre alle brugernavne og passwords
// Sårbar til data manipulation
$user_id = $_GET['id'];
$query = "DELETE FROM users WHERE id = $user_id";
mysqli_query($conn, $query);
// Attacker kan bruge: 1 OR 1=1
// Dette vil slette ALLE brugere!
// Sårbar til information disclosure
$category = $_GET['category'];
$query = "SELECT * FROM articles WHERE category = '$category'";
$result = mysqli_query($conn, $query);
if (!$result) {
// Viser detaljerede fejlmeddelelser
die("Database error: " . mysqli_error($conn));
}
// Fejlmeddelelser kan afsløre database-struktur
?>Sikker Kode (ANBEFALET)
Best practices for sikker implementation
<?php
// SIKKER KODE - Best practices for SQL injection prevention
// 1. Prepared Statements med MySQLi
$username = $_POST['username'];
$password = $_POST['password'];
// Prepared statement med placeholders
$stmt = $conn->prepare("SELECT id, username, password_hash FROM users WHERE username = ?");
$stmt->bind_param("s", $username); // "s" for string type
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 1) {
$user = $result->fetch_assoc();
// Brug password_verify() til at checke hashed passwords
if (password_verify($password, $user['password_hash'])) {
echo "Login successful!";
$_SESSION['user_id'] = $user['id'];
} else {
echo "Invalid credentials";
}
} else {
echo "Invalid credentials";
}
$stmt->close();
// 2. Prepared Statements med PDO (anbefalet)
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "username", "password");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Named parameters
$search = $_GET['search'];
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");
$stmt->execute(['search' => "%{$search}%"]);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo htmlspecialchars($row['name']) . "<br>";
}
} catch (PDOException $e) {
// Log fejlen internt, men vis generisk meddelelse
error_log("Database error: " . $e->getMessage());
echo "An error occurred. Please try again later.";
}
// 3. Sikker DELETE operation med type validation
$user_id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if ($user_id === false || $user_id === null) {
die("Invalid user ID");
}
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ? AND role != 'admin'");
$stmt->execute([$user_id]);
echo $stmt->rowCount() . " user(s) deleted";
// 4. Whitelist validation for column names og ORDER BY
$allowed_columns = ['name', 'price', 'created_at'];
$sort_column = $_GET['sort'] ?? 'name';
if (!in_array($sort_column, $allowed_columns, true)) {
$sort_column = 'name';
}
// Column names kan ikke bindes som parameters, så vi bruger whitelist
$stmt = $pdo->prepare("SELECT * FROM products ORDER BY {$sort_column} ASC");
$stmt->execute();
?>Almindelige Fejl
- ⚠At bruge mysqli_real_escape_string() som primær forsvar i stedet for prepared statements - dette er ikke tilstrækkeligt mod alle angreb
- ⚠At escapee input kun på nogle dele af applikationen, men glemme det i andre (f.eks. admin-paneler)
- ⚠At tro at input-validering alene er nok beskyttelse - du skal BÅDE validere OG bruge prepared statements
- ⚠At bygge dynamiske queries med column names eller table names uden whitelist-validering
- ⚠At vise detaljerede database-fejlmeddelelser til slutbrugere, hvilket afslører database-struktur
- ⚠At bruge emulated prepared statements i stedet for ægte prepared statements (særligt i ældre PHP-versioner)
Forebyggelsesteknikker
- 🛡️Brug prepared statements med parameteriserede queries for ALLE database-interaktioner
- 🛡️Implementer strict input validation med whitelists for alle brugerinput
- 🛡️Brug ORM frameworks som Eloquent eller Doctrine der håndterer SQL-sikkerhed automatisk
- 🛡️Konfigurer database-brugere med minimum nødvendige rettigheder (read-only hvor muligt)
- 🛡️Implementer Web Application Firewall (WAF) med SQL injection detection rules
- 🛡️Aktivér parameteriserede logging og monitorer for SQL injection-mønstre
- 🛡️Brug static code analysis værktøjer til at identificere potentielle SQL injection-sårbarheder
- 🛡️Implementér content security policies og input-længdebegrænsninger
Testing Metoder
- 🔍Manuel testing med klassiske SQL injection payloads (', ", OR 1=1, UNION SELECT, etc.)
- 🔍Automated scanning med værktøjer som SQLMap, Burp Suite, eller OWASP ZAP
- 🔍Code review med fokus på alle database-interaktioner og input-håndtering
- 🔍Penetration testing af erfarne sikkerhedseksperter
- 🔍Brug af static analysis værktøjer som Psalm, PHPStan med sikkerhedsregler
Quick Info
- ⚠NoSQL Injection - Lignende angreb mod NoSQL databaser
- ⚠LDAP Injection - Injection i LDAP queries
- ⚠Command Injection - Injection af OS-kommandoer
⚠️Sikkerhedsadvarsel
Sikkerhedssårbarheder kan have alvorlige konsekvenser. Test altid grundigt i et sikkert miljø og implementer alle anbefalede forebyggelsesteknikker.