# Forhindr SQL-injection i PHP

> Forhindr SQL-injection i PHP: Prepared Statements med PDO og mysqli, hvidlister til kolonner, knappe databaserettigheder. Med kodeeksempler og tjekliste.

Kilde: https://phpsos.de/da/ratgeber/sql-injection-php/  
Status: 25.10.2026  
Udbyder: PHP SOS, PHP-nødhjælp og serversikkerhed. Akut hjælp på WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20har%20brug%20for%20hj%C3%A6lp%20med%20vores%20PHP-server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kort sagt:** SQL-injection i PHP forhindrer I pålideligt ved aldrig at bygge værdier ind i SQL-tekster, men altid at overgive dem via Prepared Statements med pladsholdere. For kolonne- og tabelnavne, som ikke kan overgives som pladsholdere, gælder en fast hvidliste. Desuden begrænser en databasebruger med minimale rettigheder skaden, hvis en sårbarhed alligevel består.

## Hvorfor SQL-injection ikke må glemmes efter PHP2Shell

I oktober 2026 handler meget om PHP2Shell, to kritiske sårbarheder i PHP-fortolkeren, rettet siden 21.10.2026 i PHP 8.5.12, 8.4.27, 8.3.36 og 8.2.35. Mange teams har i de dage opdateret servere og genstartet processer. Det er rigtigt og presserende.

En aktuel PHP-status siger dog intet om jeres egen kode. SQL-injection hører i årevis til de hyppigste svagheder i webapplikationer og føres i OWASP Top 10 under kategorien Injection. Den, der alligevel planlægger sikkerhedskontroller, bør samtidig se på databaseadgangene i sin egen applikation.

Den gode nyhed: SQL-injection er en af de få svagheder, som kan udelukkes næsten fuldstændigt med en enkel, konsekvent anvendt regel.

## Hvordan opstår SQL-injection i PHP?

SQL-injection opstår, når en applikation behandler brugerinput som en del af en SQL-kommando i stedet for som rene data. Typisk er sammensætningen af en forespørgsel af tekstbidder og variabler. Kan en besøgende bestemme indholdet af en sådan variabel, kan vedkommende under visse omstændigheder ændre kommandoens betydning.

Følgerne rækker fra udlæsning af fremmede kundedata over ændring af priser eller rettigheder til sletning af hele tabeller. Afhængigt af database og rettigheder kan en sårbarhed også muliggøre adgang ud over databasen. Er personoplysninger berørt, kan der opstå en anmeldelsespligt efter GDPR.

Input er her ikke kun formularfelter. Også URL-parametre, cookies, HTTP-headere, uploadede filer, svar fra fremmede grænseflader og allerede gemte data fra egen database regnes som ikke-troværdige.

## Prepared Statements med PDO og mysqli

Prepared Statements sender SQL-kommandoen og værdierne adskilt til databasen. Værdierne fortolkes aldrig som en del af kommandoen. Med PDO ser en sikker forespørgsel sådan ud:

```php
<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
    'id'     => (int) $_GET['id'],
    'status' => 'aktiv',
]);
$kunde = $stmt->fetch();
```

Indstillingen `PDO::ATTR_EMULATE_PREPARES => false` sørger for, at databasen bruger ægte Prepared Statements, så vidt driveren understøtter det. Med mysqli gælder samme princip:

```php
<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();
```

Også lister til `IN (...)` kan afbildes sikkert ved at oprette en egen pladsholder for hver værdi og overgive værdierne adskilt.

```php
<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);
```

## Særtilfælde: kolonnenavne, sortering og rå SQL

Pladsholdere virker kun for værdier, ikke for tabelnavne, kolonnenavne eller sorteringsretninger. Netop her opstår i praksis mange sårbarheder, f.eks. ved sorterbare tabeller eller filtre. Løsningen er en fast liste over tilladte værdier:

```php
<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte   = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';

$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");
```

Frameworks og CMS har egne værktøjer med. I WordPress overtager `$wpdb->prepare()` denne opgave, i Laravel og Symfony query builderne og ORM'erne. Så snart I dér bruger rå SQL, f.eks. via funktioner med „raw“ i navnet, gælder reglerne i dette afsnit igen. Henvisninger til Laravel finder I under [Sikr Laravel-applikationer](https://phpsos.de/da/plattformen/laravel/).

> **Tip:**
> Søg målrettet i koden efter SQL-tekster, hvori der sættes variabler ind. En enkel start er en søgning efter `query(` og `->raw` i kombination med `$_GET`, `$_POST` eller `$_REQUEST`.

## Opspor SQL-injection i eksisterende kode

Ved ny software kan reglen overholdes fra begyndelsen. Sværere er det ved applikationer, der er vokset gennem årene. Dér betaler en systematisk søgning sig efter steder, hvor SQL-tekster sættes sammen med variabler:

```bash
# SQL-kommandoer, hvori der sættes variabler ind
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Direkte brug af forespørgselsdata i forespørgselsfunktioner
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
```

Ikke ethvert fund er en sårbarhed, f.eks. hvis variablen kun indeholder et fast tabelnavn. Hvert fundsted bør dog én gang kontrolleres bevidst og om nødvendigt omstilles til Prepared Statements. Værktøjer til statisk kodeanalyse hjælper med varigt at genkende sådanne mønstre i build-pipelinen, så nye fejl slet ikke går live.

## Begræns skaden: rettigheder, fejlmeddelelser, kontrol

Sikker kode er den første forsvarslinje. Yderligere niveauer begrænser skaden, hvis en sårbarhed alligevel overses:

- **Minimale rettigheder:** Websitets databasebruger har som regel kun brug for `SELECT`, `INSERT`, `UPDATE` og `DELETE` på egen database, ingen rettigheder til at oprette brugere eller til at tilgå andre databaser.
- **Ingen fejlmeddelelser udadtil:** `display_errors = Off` i php.ini. Databasefejl hører hjemme i loggen, ikke i browseren.
- **Adskillelse pr. applikation:** Hvert website får en egen databasebruger.
- **Hold afhængigheder aktuelle:** Sårbarheder sidder også i biblioteker, som vejledningen [composer audit](https://phpsos.de/da/ratgeber/composer-abhaengigkeiten-pruefen/) viser.
- **Lad det kontrollere:** Kodegennemgang og automatiseret analyse finder mange steder, en penetrationstest viser, hvad der faktisk kan nås udefra.

```sql
-- Eksempel: databasebruger kun med de nødvendige rettigheder
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
```

En beslægtet svaghed berører udlæsningen af data i browseren. Hvordan I sikrer den, forklarer vejledningen [Forhindr cross-site scripting i PHP](https://phpsos.de/da/ratgeber/xss-php/).

## Hvordan hjælper PHP SOS konkret?

- Vi gennemgår systematisk jeres applikations databaseadgange for usikre forespørgsler.
- Vi prioriterer fund efter tilgængelighed og berørte data og viser konkrete rettelser.
- Vi bringer databaserettigheder og fejlhåndtering op på et sikkert niveau.
- Vi tester igen efter rettelsen og dokumenterer resultatet.

En uafhængig kontrol beskriver ydelsen [Sikkerhedsaudit og penetrationstest](https://phpsos.de/da/leistungen/sicherheitsaudit-penetrationstest/).


## Ofte stillede spørgsmål

**Er det nok at behandle input med addslashes eller real_escape_string?**  
Nej, det er fejlbehæftet. Escaping afhænger af tegnsættet og konteksten og glemmes let ét sted. Prepared Statements adskiller grundlæggende kommando og data og er derfor den anbefalede vej.

**Beskytter en ORM automatisk mod SQL-injection?**  
I vid udstrækning, så længe I bruger ORM'ens forespørgselsfunktioner. Så snart I skriver rå SQL eller sætter værdier ind i sorteringer og kolonnenavne, gælder de samme regler som uden ORM.

**Hjælper en Web Application Firewall mod SQL-injection?**  
En WAF kan fange kendte mønstre og skaffe tid. Den erstatter dog ikke sikker kode, for regler på grundlag af forespørgselsdata kan omgås og rammer til tider legitimt input.

**Lukker PHP-opdateringen mod PHP2Shell også SQL-injection-sårbarheder?**  
Nej. PHP2Shell berører PHP-fortolkeren. SQL-injection er en fejl i applikationskoden og består, uanset hvilken PHP-version der kører.

## Kilder

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: Sikkerhedsmeddelelser](https://www.php.net/)
