— Artikel — № 063

063 —Tooling

phpMyAdmin in 2026: drie instellingen die het veilig houden

De helft van de senior engineers waar we mee werken wil phpMyAdmin weg in 2026. De andere helft gebruikt het wekelijks. De drie instellingen die de discussie beslechten.

Bovenaanzicht van phpMyAdmin config op linnen met rood omcirkelde regels, messing plaatje, manilla map, liniaal, potlood, lakzegel.
Hero · gestileerd stilleven№ 063

Vrijdagmiddag, vorige maand. Een agency waar we mee werken krijgt een telefoontje van een Nederlandse retailer: de checkout van een WooCommerce-shop is stuk, conversies liggen al veertig minuten stil, en de supportinbox loopt vol. De senior dev SSH't in en pakt wp-cli. De hosting hanteert een memory limit van 64MB op CLI-processen; wp option update sterft voordat het de autoloaded options uit heeft geparsed. Hij opent phpMyAdmin, draait één UPDATE wp_options, en de checkout doet het binnen twee minuten weer.

Het gesprek na het incident liep zoals te verwachten viel. De CTO wilde phpMyAdmin van de server af, "omdat het 2026 is". De dev die de storing oploste wees erop dat niets anders op die bak het zo snel had gefikst. Allebei hadden ze gelijk, op een verschillende manier, en precies over dat meningsverschil gaat deze post.

De argumenten tegen kloppen grotendeels, en zijn alsnog verkeerd

De standaardbezwaren tegen phpMyAdmin in 2026 zijn reëel. Het is een van de meest betrouwbaar gescande URLs op het publieke internet. Default installs op /phpmyadmin krijgen binnen uren na livegang credential stuffing over zich heen. De changelogs bevatten een lange reeks CVEs, inclusief af en toe een unauthenticated remote code path. De UI laat je dingen doen die niemand in productie ooit op één klik afstand van een "Go"-knop zou moeten kunnen, zoals de wp_users tabel droppen of een willekeurige file import draaien.

Niets daarvan is een argument om het weg te halen. Het is een argument om het te configureren. Hetzelfde geldt voor SSH, dat we op elke bak open laten staan, en voor WordPress zelf, dat een slechter CVE-trackrecord heeft dan phpMyAdmin en dat we tóch draaien omdat het alternatief is om geen website te hebben.

Op een legacy stack is phpMyAdmin de enige browser-interface die overleeft, wat voor bizarre PHP-versie, ontbrekende extensie of kernel-rariteit de server ook biedt. Het draait waar Adminer draait. Het draait waar MySQL Workbench weigert te tunnelen. Het draait om 23:41 op een zaterdag wanneer de ops engineer op een telefoon zit in een afgelegen gebied zonder mysql client geïnstalleerd. Het achter de hand houden is een hedge tegen een toekomst die vreemder blijkt dan je had gehoopt.

De drie instellingen hieronder zijn wat we uiteindelijk op elke server toepassen die we beheren. Twee zitten in de Apache-laag, één in de eigen config van phpMyAdmin. Niets ervan is bijzonder slim. Met z'n drieën maken ze het verschil tussen "we houden phpMyAdmin" en "we houden een publiek aanvalsoppervlak".

Instelling één: haal het van het voor de hand liggende pad

Het eerste wat elke scanner doet, is /phpmyadmin, /pma, /dbadmin, /sql en zo'n veertig varianten opvragen. Op het standaardpad zitten betekent dat je access log permanent vol probes staat, en je auth-laag permanent werk doet om ze te weigeren. Elke probe is ook een kans voor een toekomstige CVE om binnen te komen voordat jij hebt gepatcht.

De fix is een Apache Alias naar een pad dat geen enkele relatie heeft met een woordenboek-woord. Genereer eenmalig een korte random suffix, zet hem in je password manager, en spreek er nooit over in chat:

Alias /__pma_4f7a2c91 /usr/share/phpmyadmin
<Directory /usr/share/phpmyadmin>
    Options None
    AllowOverride None
    Require all granted
</Directory>

# Block the default paths so scans return 404 from Apache before PHP runs.
RedirectMatch 404 ^/(phpmyadmin|pma|dbadmin|sql|mysql)/?$

Dit is geen security through obscurity in de smalende betekenis. Het is load reduction. De probes blijven binnenkomen; ze raken een 404 op de webserver en bereiken PHP nooit, raken je auth-code nooit, komen nooit in je applicatie-logs terecht. De mod_alias documentation van Apache dekt de randgevallen als je verder moet gaan dan de basis hierboven met slash-handling.

Instelling twee: twee auth-lagen, niet één

De eigen login van phpMyAdmin is prima. Hij heeft rate-limiting, salt, logging. Wat hij niet doet, is voorkomen dat een toekomstige authentication bypass CVE een aanvaller vrij spel geeft op je database. Dus zet je er een tweede, volledig losse laag voor, en accepteer je dat twee wachtwoorden de prijs is voor het houden van de tool.

De goedkope versie is HTTP Basic Auth in de Apache-laag. De credentials staan in een bestand dat de database-user niet beheert, de prompt komt voordat PHP start, en elke phpMyAdmin-interne bypass moet eerst langs Basic:

<Location /__pma_4f7a2c91>
    AuthType Basic
    AuthName "Restricted"
    AuthUserFile /etc/apache2/.pma_htpasswd
    Require valid-user
</Location>

Genereer het bestand met htpasswd -B -c /etc/apache2/.pma_htpasswd opsadmin. De -B forceert bcrypt; de default is nog steeds MD5, wat inmiddels actief slecht is. Heb je een VPN of een kantoor-IP-blok, dan is een Require ip regel strenger en de moeite waard om er bovenop te zetten.

Instelling drie: zet uit wat de GUI je niet zou moeten laten doen

De derde instelling missen de meeste mensen, omdat phpMyAdmin het prima doet zonder. Standaard laat de interface een ingelogde gebruiker SQL importeren vanaf elke plek op het filesystem van de server, shell-commando's draaien via een paar legacy plugins, het eigen MySQL-wachtwoord wijzigen, en queries van uren draaien die de bak vastzetten. Niets daarvan is bereikbaar vanaf het publieke internet, maar het is wel bereikbaar vanuit een gestolen session, en het is bereikbaar vanuit een toekomstige CVE.

De fix zit in /etc/phpmyadmin/config.inc.php, of in config.user.inc.php op Debian-gebaseerde installs:

// Lock the server-side file import/export.
$cfg['UploadDir'] = '';
$cfg['SaveDir']   = '';

// Stop the GUI from offering "change password" links.
$cfg['ShowChgPassword'] = false;

// Cap query runtime so a fat join cannot pin a CPU for an hour.
$cfg['ExecTimeLimit'] = 300;

// Refuse to authenticate any MySQL account without a password,
// even if one slips into your grants table.
$cfg['Servers'][$i]['AllowNoPassword'] = false;

// Pin which MySQL users can log in via phpMyAdmin at all.
$cfg['Servers'][$i]['AllowDeny']['order'] = 'deny,allow';
$cfg['Servers'][$i]['AllowDeny']['rules'] = array(
    'deny  % from all',
    'allow opsadmin from all',
);

Het laatste blok levert het meeste op. Het betekent dat zelfs als een aanvaller het root MySQL-wachtwoord van een dev via phishing buitmaakt, phpMyAdmin hem niet zal inloggen tenzij de username ook matcht met je allow-regel. Gecombineerd met instelling twee zijn dat drie losse credentials die de aanvaller in handen moet hebben voordat hij bij een SQL prompt komt. De moeite waard om de full config reference eens door te nemen; er staan nog een half dozijn vlaggen in die in jouw specifieke omgeving het omzetten waard zijn.

Wat overblijft na de drie instellingen

Hierna is phpMyAdmin niet langer de felrode stip op je attack surface map. Het is een van meerdere tools achter twee auth-lagen op een onraadbaar pad met een ingeperkte GUI. Het restrisico is hetzelfde als voor elke webapp die je draaiend houdt: een 0-day, een back-up van de bak waar het htpasswd-bestand uit lekt, een dev met shoulder-surfing-problemen in de trein. Dat risicoprofiel is normaal. Het heeft dezelfde vorm als de WordPress admin die ernaast staat.

De reden dat we hierop bleven hameren op klantservers, in plaats van phpMyAdmin te vervangen door een nieuwe tool, was dat de alternatieven de omgeving niet overleven. Adminer is één bestand en goed, maar de single-page UI verstopt multi-table operaties achter een paar klikken. De mysql client over een SSH-tunnel is het juiste antwoord wanneer het werkt en een setup-probleem van 45 minuten wanneer de hosting port forwarding blokkeert. Niets daarvan helpt de dev om 23:41 op een zaterdag.

Toen we Pier bouwden voor het bewerken van verouderde sites via chat, liepen we tegen exact dit patroon aan vanaf de andere kant: de helft van de agencies die we onboardden had phpMyAdmin al opgegeven en deed chirurgie via de tools- en database-schermen van de WordPress admin, wat op elk vlak slechter is. De MySQL editor in Pier is wat we wilden dat phpMyAdmin standaard was geweest, met de versiegeschiedenis die we er altijd al in hadden willen hebben, maar op een legacy stack is de juiste zet meestal phpMyAdmin houden en hardenen in plaats van het eruit te trekken.

Voor je dit tabblad sluit: open je eigen server, kijk of phpMyAdmin nog op /phpmyadmin staat, en zo ja, doe instelling één. De andere twee kunnen tot morgen wachten. De padwijziging levert je de grootste enkelvoudige daling in scanner-load op en kost je zo'n vier minuten.

— Vragen —

Waarom dan niet gewoon Adminer gebruiken?

Adminer is uitstekend voor snelle lookups, maar de single-page UI verstopt multi-table edits, foreign-key tracing en import flows achter extra klikken. Op legacy estates heeft phpMyAdmin nog steeds een breder feature-aanbod.

Is phpMyAdmin naar een random pad verplaatsen niet gewoon security through obscurity?

Deels, maar de echte winst zit in load reduction. Probes raken een 404 op Apache voordat PHP start, dus je auth-laag stopt met werk doen voor scanners en je logs verzuipen niet meer in ruis.

Heeft phpMyAdmin een eigen two-factor optie?

Ja, sinds 4.8 ondersteunt het TOTP en hardware keys per account. De moeite waard om aan te zetten, maar het vervangt de Basic Auth-laag ervoor niet, want 2FA komt pas in actie wanneer PHP al draait.