Kort sagt: PHP tilgår databaser via to udbredte grænseflader: PDO (PHP Data Objects) arbejder med mange databasesystemer via en ensartet grænseflade, mysqli er skåret til MySQL og MariaDB. Med begge beskytter prepared statements, altså forespørgsler med pladsholdere, pålideligt mod SQL-injection. Siden PHP 8.0 (PDO) hhv. 8.1 (mysqli) melder begge fejl som exception som standard.
Sådan taler PHP med databaser
Næsten enhver PHP-applikation gemmer sit indhold i en database: sider, ordrer, brugerkonti, indstillinger. PHP-scriptet opretter hertil en forbindelse, sender en forespørgsel i sproget SQL og behandler resultatet. Adgangsdata står som regel i en konfigurationsfil på den samme server.
For direktionen er det vigtigt, fordi databasen som regel indeholder virksomhedens mest værdifulde data. Den, der kan udføre kode på webserveren, kan som regel også læse konfigurationsfilen og med disse adgangsdata få adgang til databasen.
PDO og mysqli sammenlignet
| Kendetegn | PDO | mysqli |
|---|---|---|
| Databaser | mange, via drivere som PDO_MYSQL, PDO_PGSQL, PDO_SQLITE, PDO_SQLSRV, PDO_OCI |
MySQL og MariaDB |
| Pladsholdere | navngivne (:name) og positionelle (?) |
positionelle (?) |
| Stil | objektorienteret | objektorienteret og proceduremæssig |
| Fejl som exception | standard siden PHP 8.0.0 | standard siden PHP 8.1.0 |
| Kortform til forespørgsler med parametre | prepare() og execute() med array |
execute_query() siden PHP 8.2.0 |
I praksis afgør ofte frameworket eller CMS'et valget. Mange medbringer et eget databaselag, der internt bruger PDO eller mysqli. Den, der overtager en applikation, kan med en enkel søgning i kildekoden fastslå, hvilken grænseflade der bruges, og hvor forespørgsler opstår:
# Hvor opretter applikationen forbindelser, hvor opstår forespørgsler?
grep -rn --include='*.php' -E 'new PDO|new mysqli|mysqli_connect|->query\(' /var/www/app/src | head -50
Fund med ->query( er ikke automatisk usikre. De fortjener dog et ekstra blik, fordi værdier dér kan havne direkte i SQL-teksten i stedet for at blive overdraget via pladsholdere. Netop ældre projekter blander begge stilarter, og kontrollen kan betale sig før enhver større opdatering.
Prepared statements: det vigtigste mønster
Ved et prepared statement sendes forespørgslen med pladsholdere til databasen, og værdierne overdrages adskilt. Ifølge PHP-manualen beskytter det mod SQL-injection, når alle brugerindtastninger går via pladsholdere.
<?php
declare(strict_types=1);
$pdo = new PDO(
'mysql:host=localhost;dbname=shop;charset=utf8mb4',
$dbUser,
$dbPass,
[PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
);
$stmt = $pdo->prepare('SELECT id, name FROM kunden WHERE plz = :plz AND aktiv = :aktiv');
$stmt->execute(['plz' => $plz, 'aktiv' => 1]);
foreach ($stmt as $zeile) {
echo htmlspecialchars($zeile['name'], ENT_QUOTES, 'UTF-8'), "\n";
}
Det samme med mysqli i den kortform, der findes siden PHP 8.2:
<?php
$db = new mysqli('localhost', $dbUser, $dbPass, 'shop');
$ergebnis = $db->execute_query('SELECT id, name FROM kunden WHERE plz = ?', [$plz]);
foreach ($ergebnis as $zeile) {
echo htmlspecialchars($zeile['name'], ENT_QUOTES, 'UTF-8'), "\n";
}
To grænser bør udviklere kende. Pladsholdere må ikke stå i anførselstegn; ved LIKE-søgninger hører jokertegnene % hjemme i den overdragne værdi. Og pladsholdere erstatter kun værdier, ikke tabel- eller kolonnenavne. Hvordan man løser sådanne særtilfælde sikkert, viser guiden Forhindre SQL-injection i PHP.
Fejlhåndtering: exceptions i stedet for stille fejl
Tidligere arbejdede begge grænseflader som standard stille: En mislykket forespørgsel leverede false, og den, der ikke kontrollerede det, arbejdede videre med tomme data. Det har ændret sig:
- PDO: Siden PHP 8.0.0 er
PDO::ERRMODE_EXCEPTIONstandard. Fejl kaster enPDOException. - mysqli: Siden PHP 8.1.0 er fejlmeldingen sat til
MYSQLI_REPORT_ERRORogMYSQLI_REPORT_STRICT. Fejl kaster enmysqli_sql_exception; tidligere var meldingen slået fra.
For sikkerheden betyder det: Exceptions skal opfanges og logges, men må aldrig havne i browseren med SQL-tekst, tabelnavne eller adgangsdata. Hvordan man adskiller det rent, beskriver siden Fejl og exceptions i PHP.
Tildel adgangsdata og rettigheder rigtigt
Databasen er kun så godt beskyttet som dens adgangsdata. Disse regler har vist sig at fungere:
- Egen bruger pr. applikation: Brug aldrig databasens administrator til websitet.
- Kun nødvendige rettigheder: Et website har som regel brug for at læse, skrive, ændre og slette i sin egen database, men ingen rettigheder til andre databaser eller serverfunktioner.
- Ingen fjernforbindelse uden grund: Databasen bør kun kunne nås fra webserveren, ikke fra internettet.
- Adgangsdata uden for webroot: Konfigurations- og
.env-filer må ikke kunne hentes via URL. Mere herom i indlægget Beskyt adgangsdata og .env-filer. - Krypteret forbindelse ved adskilte servere: Ligger databasen på en anden maskine, bør forbindelsen køre krypteret, så adgangsdata og indhold ikke går i klartekst gennem nettet.
- Egne adgange til vedligeholdelse: Datasikkerhedskopiering, analyser og administratorer får egne brugere. Så kan man i loggen spore, hvem der har gjort hvad, og en adgang kan spærres uden at forstyrre websitet.
Databaser, PHP2Shell og alvorsfaldet
PHP2Shell berører ikke selve databasen, men PHP-fortolkeren: CVE-2026-109618 og CVE-2026-109619 tillader udførelse af skadelig kode uden login. Lykkes det, kører koden med PHP-procesens rettigheder og kan dermed som regel læse konfigurationsfilen med databasens adgangsdata. Prepared statements hjælper ikke mod det, fordi angrebet slet ikke løber via en SQL-forespørgsel.
Pligt er derfor opdateringen til PHP 8.5.12, 8.4.27, 8.3.36 eller 8.2.35 og genstarten af alle PHP-processer. Er der tegn på et vellykket angreb, hører nye databaseadgangsdata, en kontrol af databasen for indsmuglet indhold og en vurdering af anmeldelsespligten efter GDPR med. Her giver en struktureret Incident Response mening.
Skift først databaseadgangskoderne efter oprydningen af serveren. Så længe skadelig kode kører, kan den også læse nye adgangsdata ud igen.
Hvordan hjælper PHP SOS konkret?
- Vi kontrollerer, hvordan jeres applikation tilgår databasen, og om bruger, rettigheder og netværksadgang svarer til minimumskravet.
- Vi kontrollerer, om konfigurations- og adgangsfiler ligger uden for webroot og ikke er offentligt tilgængelige.
- Efter en hændelse fornyer vi adgangsdata i den rigtige rækkefølge og kontrollerer databasen for indsmuglet kode.
- Vi dokumenterer resultaterne, så databeskyttelsesansvarlige og forsikringsselskaber kan følge dem.
Ofte stillede spørgsmål
Skal jeg bruge PDO eller mysqli?
Til nye projekter er PDO som regel det mere fleksible valg, fordi det arbejder med mange databaser via den samme grænseflade. mysqli giver mening, hvis I udelukkende bruger MySQL eller MariaDB og har brug for deres specialfunktioner. Begge er sikre, hvis prepared statements bruges konsekvent.
Kaster PDO automatisk exceptions ved fejl?
Ja, siden PHP 8.0.0 er PDO::ERRMODE_EXCEPTION standard. Tidligere var det den stille tilstand, hvor fejl kun kunne hentes via errorCode() og errorInfo(). Ældre applikationer sætter ofte stadig tilstanden udtrykkeligt.
Kan pladsholdere også erstatte tabel- eller kolonnenavne?
Nej. Pladsholdere står kun for værdier. Tabel- og kolonnenavne, f.eks. til en sortering, skal kontrolleres mod en fast liste over tilladte navne, før de sættes ind i forespørgslen.
Skal jeg skifte databaseadgangskoden efter PHP2Shell?
Opdateringen alene kræver det ikke. Er der dog tegn på, at serveren var kompromitteret, regnes alle adgangsdata i konfigurationsfiler som kendte. Så bør I fornye dem efter oprydningen.