Terug naar hoofdinhoud
Joomla-website gehackt: wat je nu moet doen
Op deze pagina

Joomla-website gehackt: wat je nu moet doen

09 augustus 2026

Vroeg of laat overkomt het iemand die je kent, en op een dag kan het jou overkomen: je opent je website en die is niet meer van jou. Er verschijnen vreemde pagina’s in Google, bezoekers worden doorgestuurd naar een goksite, of je hostingprovider schakelt het account uit met een kort e-mailtje over "kwaadwillige activiteiten". De komende uren bepalen of dit een slechte middag of een slechte maand wordt.

Dit artikel legt uit wat je doet als je Joomla-website gehackt is. Het behandelt de eerste stappen voor eigenaren en redacteuren, het onderzoek en de opschoning voor beheerders, en de forensische details voor ontwikkelaars. Je leert hoe je een hack herkent, hoe je de schade beperkt zonder het bewijs te vernietigen, hoe je het toegangspunt en de achterdeurtjes vindt, hoe je kiest tussen opschonen en opnieuw opbouwen, hoe je de site veilig terugbrengt, en wie je daarna moet informeren.

Het opschonen van een gehackte website zonder het toegangspunt te achterhalen, is geen oplossing. Het is een uitnodiging om terug te komen.

Het doel is simpel: je een rustig, geordend plan geven voor een moment waarop paniek de natuurlijke reactie is.

1. De basis

1.1 Wat "gehackt" eigenlijk betekent

Een gehackte website is een website waarop iemand anders code kan draaien binnen jouw hostingaccount. Dat is de kern. Alles wat je aan de oppervlakte ziet - spampagina's, doorverwijzingen, een beklad startscherm - is alleen het zichtbare gebruik van een toegang die er al is.

Dat is belangrijk voor hoe je reageert. Het weghalen van de spampagina's haalt de toegang niet weg. Zolang de aanvaller een ingang houdt, zet hij de spam gewoon terug, meestal binnen een paar dagen.

De meeste hacks vallen in een paar categorieen:

TypeWat de aanvaller wilWat jij merkt
SEO-spam Backlinks en posities voor hun eigen klanten. Verborgen pagina's over medicijnen, leningen of casino's in Google.
Doorverwijzingen Verkeer dat ze kunnen verkopen. Bezoekers vanuit Google komen ergens anders uit, maar jij ziet een normale site.
Phishing Inloggegevens van klanten van andere bedrijven. Een inlogpagina van een bank of pakketdienst in een map die jij nooit hebt gemaakt.
Mailmisbruik Een schone server om spam vanaf te sturen. Je domein komt op blocklists; e-mail komt niet meer aan.
Cryptomining of botnet Jouw CPU. De site wordt traag; je hoster klaagt over het verbruik.
Defacement Aandacht. Je homepage vervangen door een boodschap. Zeldzaam, en tenminste eerlijk.
Datadiefstal Je gebruikerstabel, je klantgegevens. Vaak helemaal niets. Dit is degene met juridische gevolgen.

Let op het patroon: in de meeste categorieen probeert de aanvaller de site juist werkend te houden. Een kapotte site wordt gerepareerd. Een werkende site levert maandenlang stilletjes geld op.

1.2 Signalen dat je Joomla-site gehackt is

Sommige signalen zijn duidelijk, andere praat je makkelijk weg. Neem ze allemaal serieus:

  • Google Search Console meldt "Beveiligingsproblemen" of nieuwe pagina's die jij nooit hebt gemaakt.
  • Zoekresultaten voor je domein tonen titels in een andere taal of van een ander bedrijf.
  • Bezoekers melden doorverwijzingen, maar jij krijgt ze niet reproduceerd als je direct naar de site gaat. Veel aanvallen slaan alleen aan bij bezoekers die uit een zoekmachine komen, of alleen op mobiel.
  • Je browser of antivirus waarschuwt voor de site.
  • Er bestaat een Super User die je niet kent, of een bestaand account heeft ineens meer rechten.
  • Er verschijnen PHP-bestanden in images/, tmp/ of cache/, mappen waar nooit code hoort te staan.
  • Wijzigingsdatums in de Joomla core-mappen zijn recent, terwijl jij niets hebt bijgewerkt.
  • Uitgaande mail bounct, of je domein staat op een spam-blocklist.
  • Je hoster zet het account stil of stuurt een abuse-melding.
  • Serverbelasting of bandbreedte schiet omhoog zonder dat het bezoek meegroeit.

Een enkel vreemd signaal kan een onschuldige oorzaak hebben. Twee of drie tegelijk bijna nooit.

1.3 De eerste regel: niet meteen verwijderen

De natuurlijke reactie is het vreemde bestand zoeken en direct weggooien. Doe dat niet. Dat vreemde bestand is ook je bewijs, en het draagt een tijdstempel die je vertelt wanneer de inbraak plaatsvond. Verwijder je het, dan verlies je de draad die naar het toegangspunt leidt.

Werk in plaats daarvan in deze volgorde, en maak elke fase af voordat je aan de volgende begint:

  1. Beperken - stop de schade die nog doorloopt.
  2. Bewaren - maak een forensische kopie voordat je iets verandert.
  3. Onderzoeken - achterhaal wanneer en hoe ze binnenkwamen.
  4. Verwijderen - haal de kwaadaardige code weg en dicht het gat.
  5. Herstellen - breng de site terug en houd hem in de gaten.
  6. Melden - informeer de mensen die het recht hebben het te weten.

De rest van dit artikel volgt precies die zes fasen.

Naar boven

2. Het eerste uur: beperk de schade

2.1 Zet de site offline

Zolang de site bereikbaar is, blijft hij bezoekers schaden, spam versturen en straffen van zoekmachines verzamelen. Zet hem uit.

De beste methode hangt af van hoe ernstig het is:

MethodeWaarWanneer gebruiken
Joomla Website offline Systeem → Globale configuratie → Website offline Lichte gevallen. Joomla draait nog, dus geinjecteerde code in Joomla draait ook nog.
Blokkade op serverniveau .htaccess-regels of een onderhoudspagina van je hoster Beter. PHP wordt overgeslagen voor bezoekers en de site is echt dicht.
De site buiten bereik zetten Shell: verplaats de map en zet hem op 700 Het beste als je SSH hebt. Er wordt niets geserveerd, niets verwijderd, alles bewaard.
Account opschorten Hosting-controlepaneel Ernstige gevallen: phishingpagina's, actief mailmisbruik, of een eis van je hoster.

De offline-modus van Joomla is de zwakste van de drie. Hij houdt gewone bezoekers tegen, maar een achterdeur in een PHP-bestand wordt direct via een URL benaderd en vraagt Joomla nooit of de site offline staat. Vermoed je een webshell, blokkeer dan op serverniveau.

Heb je shelltoegang, dan is de vierde optie degene waar ik naar grijp, en degene die ik op mijn eigen gehackte site gebruikte (paragraaf 4.6), omdat het de enige methode is die het incident indamt en tegelijk het bewijs beschermt:

mv public_html public_html.hacked
chmod 700 public_html.hacked
mkdir public_html
printf '<h1>Tijdelijk niet beschikbaar</h1>' > public_html/index.html

De gehackte site is nu onbereikbaar vanaf het web, inclusief elke webshell erin, omdat de webservergebruiker de map niet meer in kan. Er is niets verwijderd, dus elk bestand en elke tijdstempel blijft bewaard voor fase 2. Bezoekers krijgen een eenvoudige statische pagina waar geen PHP aan te pas komt, dus geen Joomla, geen extensies, en niets meer dat een aanvaller kan aanroepen.

Controleer daarna als welke gebruiker je PHP eigenlijk draait. Draait PHP als hetzelfde account dat de bestanden bezit, dan geeft 700 nog steeds toegang; verplaats de map in dat geval helemaal buiten de webroot, of haal de site op hostingniveau offline.

2.2 Bewaar het bewijs voordat je iets verandert

Maak, voordat je ook maar een bestand aanraakt, een volledige kopie van de gehackte toestand: alle bestanden en een complete databasedump. Label die duidelijk als besmet en bewaar hem buiten de webroot, het liefst helemaal van de server af.

Deze kopie is geen back-up die je ooit gaat terugzetten. Het is je bewijsmateriaal. Je hebt het nodig omdat:

  • Tijdstempels van bestanden en logregels verdwijnen zodra je begint met opschonen.
  • Je de besmette bestanden wilt vergelijken met schone om elke injectie te vinden.
  • Je bij persoonsgegevens later mogelijk moet uitleggen wat er is gebeurd en wanneer.
  • Je, als je te agressief opschoont en iets sloopt, nog kunt nakijken hoe het was.

Kopieer de bestanden met een methode die wijzigingsdatums intact laat. Op de commandline behouden rsync -a of tar ze; slepen in sommige FTP-clients niet.

2.3 Licht je hostingprovider in

Neem vroeg contact op met je hostingprovider, ook als je alles zelf wilt oplossen. Twee redenen: hun serverlogs gaan meestal verder terug dan wat jij in je eigen account ziet, en een hack begint soms buiten je site - in een buuraccount, in een gestolen controlepaneel-wachtwoord, of in een kwetsbaarheid op serverniveau. Alleen je hoster kan dat nakijken.

Vraag specifiek om de ruwe access logs van de laatste maanden, de FTP- en SSH-inloghistorie van je account, en of ze hetzelfde besmettingspatroon op andere accounts zien.

Soms loopt het gesprek andersom en hoor je van de hack via een e-mail van iemand anders. Beveiligingsorganisaties scannen op beklade en kwaadaardige pagina's en melden die bij de hostingprovider en bij de domeinregistratie. Voor .nl-domeinen meldt Netcraft bevindingen bijvoorbeeld bij SIDN, de beheerder van de zone. Je hoster stuurt zo'n melding mogelijk door, of stuurt er zelf een.

Behandel die berichten als urgent, want er zit meestal een deadline aan met echte gevolgen: hostingproviders schorten accounts op, en een registratie kan de domeinnaam zelf stilleggen als niemand reageert. Antwoord meteen dat je ermee bezig bent, ook voordat je iets hebt opgelost. Een gemelde site die antwoordt wordt heel anders behandeld dan een gemelde site die stil blijft, en je zult moeten terugmelden zodra hij schoon is (paragraaf 9.6).

2.4 Zet nog geen back-up terug

De back-up van gisteravond terugzetten voelt als de snelste oplossing, en het is de meest gemaakte beginnersfout. Er gaan meestal twee dingen mis:

  • Als de site weken geleden is gehackt, zit de achterdeur ook in de back-up van gisteren. Je zet de hack terug.
  • Zelfs een echt schone back-up gaat regelrecht terug naar de kwetsbare toestand waardoor de aanvaller binnenkwam. Zonder de update of de wachtwoordwijziging ben je binnen dagen opnieuw besmet.

Een back-up terugzetten hoort bij fase 4, nadat je weet wanneer de inbraak plaatsvond en wat de oorzaak was. Niet eerder.

2.5 Stop de site met mail versturen

Wordt de site gebruikt om spam te versturen, dan beschadigt elk extra uur je domeinreputatie en duwt het je verder de blocklists op. Een domein weer van zo'n lijst af krijgen duurt veel langer dan de hack zelf.

Joomla heeft hier een schakelaar voor: zet Mail versturen (mailonline) op Nee onder Systeem → Globale configuratie → Server → Mailinstellingen. Hij staat standaard aan, en uitzetten zorgt dat Joomla helemaal niets meer verstuurt - ook je eigen contactformulieren niet, dus dit is een bewuste tijdelijke maatregel.

Let op de beperking, dezelfde als bij de offline-modus: dit stopt Joomla met mailen. Malware die de PHP-functie mail() rechtstreeks aanroept, of zelf met een SMTP-server praat, gaat niet via Joomla en negeert deze instelling volledig. Gaat de spam door nadat je hem hebt omgezet, dan is de verzender een bestand en niet Joomla, en heb je de blokkade op serverniveau uit paragraaf 2.1 nodig plus hulp van je hoster met de mailwachtrij.

2.6 Ga ervan uit dat ook de naburige websites besmet zijn

Staan er meerdere websites in hetzelfde hostingaccount, behandel ze dan vanaf het begin allemaal als gehackt, niet alleen die ene waar je iets opviel. Draaien alle sites als dezelfde Unix-gebruiker, dan kan code in de ene site meestal de bestanden van de andere lezen en schrijven, dus verspreidt de besmetting zich zijwaarts zonder tweede inbraak.

Dat verandert je eerste uur: inventariseer elke site in het account, controleer ze allemaal op de signalen uit paragraaf 1.2, en plan de opschoning voor het hele account in een keer. Ze een voor een schoonmaken terwijl de rest besmet blijft betekent dat de eerste site alweer besmet raakt terwijl jij aan de tweede werkt.

Naar boven

3. Sluit de aanvaller buiten

3.1 Vervang alle inloggegevens

Ga ervan uit dat elk wachtwoord dat deze site heeft aangeraakt bekend is bij de aanvaller. Verander ze allemaal, en doe dat vanaf een computer die je vertrouwt - als op jouw eigen machine een informatiedief draait, is dat namelijk de plek waar de wachtwoorden zijn weggelekt.

  • Alle Joomla Super User- en beheerderswachtwoorden.
  • Het wachtwoord van het hosting-controlepaneel.
  • FTP-, SFTP- en SSH-accounts, inclusief eventuele sleutels. Verwijder onbekende SSH-sleutels uit authorized_keys.
  • Het databasewachtwoord in configuration.php.
  • Web Services API-tokens, en tokens van externe koppelingen.
  • SMTP-gegevens voor uitgaande mail.
  • DNS- en CDN-accounts. Wie een DNS-record kan wijzigen heeft je site helemaal niet nodig.
  • Inloggegevens van je back-upopslag. Een aanvaller die bij je back-ups komt kan precies de schone kopie vernietigen die je zo nodig gaat hebben.
  • Deploy-tokens, Git-gegevens, en elk CI- of webhook-geheim dat op de server staat.
  • Elk account waar je datzelfde wachtwoord ook gebruikte. Hier wordt een enkele lek ineens vijf incidenten.

Bepaal het bereik voordat je begint, want de lijst hierboven is alleen een startpunt. Alles waarvan het geheim op die server stond is nu verdacht, en configuration.php is het duidelijkste voorbeeld: kon de aanvaller bestanden lezen, ga er dan van uit dat hij dat bestand las, en daarmee heeft hij de databasegegevens en de site secret in een keer. Hetzelfde geldt voor een .env-bestand, een opgeslagen deploy-sleutel, of een wachtwoord in een configuratiebestand van een andere site in hetzelfde account.

3.2 Verander de site secret

Je configuration.php bevat een $secret. Joomla gebruikt die op meer plekken dan mensen denken, en hem veranderen is een van de meest effectieve losse acties na een hack.

Twee concrete effecten, allebei terug te vinden in de core-code:

  • Alle sessies stoppen. Joomla leidt de naam van de sessiecookie af uit md5($secret . $session_name). Verander je de secret, dan wijst elke bestaande sessiecookie naar een sessienaam die niet meer bestaat, dus wordt iedereen die ingelogd is - de aanvaller inclusief - direct uitgelogd.
  • Alle API-tokens vervallen. De Joomla token-plugins controleren tokens met hash_hmac() en gebruiken de site secret als sleutel. Met een nieuwe secret faalt elk uitgegeven token, ook de tokens die de aanvaller voor zichzelf heeft aangemaakt.

Genereer een nieuwe willekeurige string van flinke lengte en vervang de waarde. Let ook op wat het niet breekt: multi-factor authenticatie staat in #__user_mfa en blijft werken, en gewone wachtwoorden veranderen niet omdat het bcrypt-hashes zijn en niet van de secret afgeleid.

3.3 Ruim sessies en onthoud-mij-cookies op

Wil je expliciet zijn in plaats van vertrouwen op het wijzigen van de secret, leeg dan de sessietabel direct:

TRUNCATE TABLE `#__session`;

Pak daarna de blijvende logins aan. De functie System - Remember Me bewaart langlopende logintokens in #__user_keys, en die overleven een gewone uitlog met opzet. Ruim ze na een hack allemaal op:

TRUNCATE TABLE `#__user_keys`;

Iedereen moet opnieuw inloggen. Dat is precies de bedoeling. Mooi meegenomen: Joomla verwijdert de rijen van een gebruiker in #__user_keys automatisch als je die gebruiker blokkeert, dus een verdacht account blokkeren doodt ook zijn blijvende cookies.

3.4 Controleer elk account

Open Gebruikers → Beheren, sorteer aflopend op Registratiedatum en bekijk alles wat rond en na de vermoedelijke inbraak is aangemaakt. Controleer daarna de groepstoewijzingen, want een stillere aanval maakt helemaal geen nieuw account aan - hij voegt een bestaand, vergeten redacteursaccount toe aan de groep Super Users.

SELECT u.id, u.username, u.email, u.registerDate, u.lastvisitDate, g.title
FROM `#__users` AS u
JOIN `#__user_usergroup_map` AS m ON m.user_id = u.id
JOIN `#__usergroups` AS g ON g.id = m.group_id
ORDER BY u.registerDate DESC;

Blokkeer wat je niet herkent in plaats van het meteen te verwijderen - een geblokkeerd account kan niet inloggen, maar je ziet het nog terwijl je onderzoekt. Let ook op accounts waarvan het e-mailadres is gewijzigd naar een adres op een domein dat je niet kent: dat is de klassieke voorbereiding op een wachtwoordreset.

Naar boven

4. Achterhaal hoe ze binnenkwamen

4.1 De Joomla actionlog

Het Gebruikersactie log (com_actionlogs, gevoed door de plugin System - User Actions Log) is de eerste plek om te kijken. Open Gebruikers → Gebruikersactie log. Het registreert logins, installaties van extensies, wijzigingen aan artikelen en modules, en opgeslagen configuraties, met een gebruikersnaam en een tijdstempel. Je kunt de hele lijst exporteren naar CSV via de werkbalk, wat in een spreadsheet veel prettiger doorwerkt.

Twee standaardinstellingen zijn belangrijk voordat je op dit logboek vertrouwt:

  • IP-logging staat standaard uit (ip_logging in de componentopties). Heeft niemand dat aangezet, dan vertelt je logboek wie en wanneer, maar niet vanwaar.
  • API-acties worden standaard niet gelogd (loggable_api). Activiteit via de Web Services API laat geen spoor na in dit logboek tenzij die optie aan stond.

Allebei zijn het schakelaars om vandaag aan te zetten, nu je toch naar dat scherm kijkt. Ze kosten niets en het is precies wat je de volgende keer had willen hebben.

Er is een patroon in dit logboek dat mensen stelselmatig over het hoofd zien, omdat het er helemaal niet uitziet als een aanval: een geslaagde login met je eigen account, op een moment dat je niet aan het werk was. Iedereen scant de lijst op onbekende gebruikersnamen. Niemand zet vraagtekens bij zijn eigen naam.

Controleer dus je eigen regel. Vergelijk elke recente login van elke beheerder met waar die persoon toen echt was, en bevestig dat met iets onafhankelijks - je browsergeschiedenis, je agenda, je telefoon. Een login die je niet kunt verklaren is een van de sterkste signalen uit dit hele artikel, en hij zegt iets specifieks: de aanvaller had je wachtwoord niet nodig. Werkt je wachtwoord nog en is het nooit gereset, dan kwamen ze binnen met een gestolen sessie, een gelekt token, of een lek waardoor ze zich als jou konden aanmelden. Alleen dat wachtwoord veranderen verwijdert ze niet, en daarom bestaan paragraaf 3.2 en paragraaf 3.3.

4.2 Het logboek met mislukte logins

De plugin System - Log luistert naar het event onUserLoginFailure en schrijft elke mislukte inlogpoging naar een logbestand. Standaard is dat error.php in je logmap (administrator/logs/, tenzij je die hebt verplaatst in de globale configuratie).

Lees het op twee patronen. Duizenden mislukkingen vanaf veel adressen is gewone ruis van bots. Een korte reeks mislukkingen op een bestaande gebruikersnaam, gevolgd door stilte, is de interessante: stilte na mislukkingen betekent vaak dat het uiteindelijk lukte.

De plugin heeft een optie log_username die de geprobeerde gebruikersnaam aan elke regel toevoegt. Zonder die optie zie je alleen dat een login mislukte, niet welk account het doelwit was. Zet hem aan.

4.3 Server access logs

Het webserverlog is waar de inbraak meestal echt zichtbaar is, omdat het verzoeken vastlegt die de Joomla-applicatie nooit hebben bereikt. Vraag je hoster om de ruwe logs en zoek naar:

  • POST-verzoeken naar bestanden die nooit een POST horen te krijgen, vooral onder images/, tmp/ of media/.
  • Verzoeken naar bestandsnamen die je niet kent, met name korte willekeurige namen die eindigen op .php.
  • Lange, gecodeerde querystrings vol %-escapes of base64-achtige tekst.
  • Verzoeken naar een bekende kwetsbare component-URL, bijvoorbeeld index.php?option=com_iets&task=... voor een extensie die je hebt geinstalleerd.
  • Het allereerste verzoek vanaf het IP-adres dat later bestanden uploadt. Dat verzoek is meestal de exploit.

Praktische truc: zodra je de tijdstempel van het oudste kwaadaardige bestand kent, grep je het access log op precies die minuut. Het verzoek dat het bestand aanmaakte staat er bijna altijd direct bij.

Verwacht meer dan een bezoeker. Zodra een site is opengebroken wordt het adres vaak gedeeld, en komen gelegenheidsgebruikers de webshell gebruiken die iemand anders heeft geplaatst. Een tweede IP-adres dat uren later opduikt, binnenkomt via een sociaal netwerk en regelrecht naar een bestand gaat dat het onmogelijk kan hebben geraden, is een bekend beeld. Het betekent niet twee losse inbraken; het betekent dat de eerste is rondverteld. Het verhoogt wel de urgentie, want elke nieuwkomer richt zijn eigen schade aan.

Heb je root op je eigen server, controleer dan ook het authenticatielogboek, om vast te stellen of de aanvaller ooit verder is gekomen dan de webapplicatie:

grep Accepted /var/log/auth.log     # geslaagde SSH- en SFTP-logins
grep Failed   /var/log/auth.log     # brute-force-pogingen

Zie je hier alleen je eigen logins, dan is dat echt goed nieuws: het wijst erop dat de hack binnen het webaccount bleef en de server zelf nooit bereikte. Let wel op de beperking, en wees daar eerlijk over: kreeg de aanvaller schrijftoegang buiten de webroot, dan had hij dit logboek kunnen aanpassen. Het is bewijsmateriaal, geen bewijs, en op shared hosting heb je het helemaal niet - vraag het dan aan je hoster.

4.4 Tijdstempels van bestanden

Wijzigingsdatums zijn het goedkoopste forensische gereedschap dat je hebt. Op een shell toon je alles wat in twee weken is veranderd:

find . -type f -name "*.php" -mtime -14 -printf "%TY-%Tm-%Td %TH:%TM  %p\n" | sort

Op een site die niet recent is bijgewerkt hoort deze lijst vrijwel leeg te zijn. Groepeer de resultaten op tijd: bestandswijzigingen komen meestal in bursts van een paar seconden, en de oudste burst markeert de inbraak.

Houd er rekening mee dat een zorgvuldige aanvaller tijdstempels terugzet met touch om op te gaan in de rest. Zien de datums er verdacht uniform uit, of is de wijzigingstijd van een bestand ouder dan de inode-wijzigingstijd (ls -lc versus ls -l), beschouw de tijdstempels dan als onbetrouwbaar en leun op bestandsvergelijking (paragraaf 5.1).

4.5 Bouw een tijdlijn

Schrijf de bevindingen op in een simpele lijst, gesorteerd op tijd: het eerste verdachte verzoek, het eerste gewijzigde bestand, het nieuwe gebruikersaccount, de eerste geindexeerde spampagina. Je zoekt de vroegste gebeurtenis zonder onschuldige verklaring. Dat is je inbraakmoment, en het beantwoordt twee vragen tegelijk: welke back-upgeneratie nog schoon is, en welke kwetsbaarheid op die datum openstond.

Kijk ook na welke Joomla-versie en extensieversies je op die datum draaide. Heel vaak ligt het antwoord daar al: een extensie met een bekende beveiligingsupdate van twee weken eerder die nooit is geinstalleerd.

Stel daarna een tweede vraag, en stel die over jezelf in plaats van over de software: wat heb ik kort voor die datum aangezet en nooit teruggezet? Een verrassend deel van de echte incidenten begint met een tijdelijke maatregel tijdens het oplossen van iets heel anders. Debug-modus aan om een mailprobleem te zoeken. Foutrapportage op maximaal. Een map even schrijfbaar gemaakt. Een testbestand geupload om iets te controleren. Elk daarvan is onschuldig voor de tien minuten die bedoeld waren, en elk is een echte opening voor de maanden die het werkelijk duurde, want wat die tien minuten beeindigt is meestal een onderbreking en geen beslissing. Loop je eigen aantekeningen, je ticketgeschiedenis en je mail van de weken voor de inbraak terug.

Dezelfde redenering verklaart hoe aanvallers een site uberhaupt vinden. Niemand had jouw kleine Joomla-site bij naam op het oog; ze zochten op waar hij lekte. Debug-uitvoer, uitgebreide foutmeldingen en mapoverzichten bevatten allemaal herkenbare tekst, en zodra een zoekmachine een pagina met die tekst indexeert, is die pagina vindbaar voor iedereen die weet waarop te zoeken. Een referrer van https://www.google.com/ bij het allereerste verzoek vanaf het IP-adres van de aanvaller is hier het teken: ze hebben je domein niet geraden, ze kregen het aangereikt van een zoekmachine. Zoek zelf op je domein naar de teksten die jouw fouten opleveren, dan zie je ongeveer wat een aanvaller ziet.

4.6 Een uitgewerkt voorbeeld: hoe dit op mijn eigen site verliep

De methode hierboven is voor mij geen theorie. In oktober 2022 werd de website van mijn eigen Joomla User Group, Arnhem-Nijmegen, gehackt, en mijn onderzoek liep precies langs deze stappen. Het is de moeite waard om door te lopen, want de site was drie maanden oud, draaide maar een handvol extensies en was volledig bijgewerkt. Bijgewerkt zijn is noodzakelijk, en het was niet voldoende.

Het eerste uur

Ik kwam er om kwart over twaalf 's nachts achter, via een e-mail. Netcraft, dat bevindingen doorgeeft aan SIDN voor de .nl-zone, had een defacement-bestand op het domein gevonden en vroeg me het op te lossen, anders zou het domein worden stilgelegd. Mijn VPS-provider was ook ingelicht en waarschuwde dat de hele VPS offline gezet kon worden. Dat is de meldroute uit paragraaf 2.3, inclusief de deadline.

Via FTP vond ik twee bestanden die wij er nooit hadden neergezet: Ox.php, de defacement, en sec.php, een achterdeur met toegang tot het bestandssysteem en de database. Ik heb de site ingedamd zoals paragraaf 2.1 beschrijft, opzij gezet met strikte rechten en een statische onderhoudspagina ervoor. Daarna heb ik de bestanden, de database en de logs gearchiveerd en naar mijn eigen machine gehaald voordat ik iets verder aanraakte. Toen ben ik gaan slapen, want deen analyse vraagt om een helder hoofd en het bewijs had ik veiliggesteld.

Wat de logs lieten zien

De volgende dag leverde de database mij het bewijs. #__action_logs toonde een geslaagde login voor mijn eigen account op 12 oktober om 15:46. Maar ik was op dat moment nergens in de buurt van een computer geweest; mijn browsergeschiedenis bevestigde dat. Mijn wachtwoord werkte nog en was nooit gewijzigd. Dat is het patroon uit paragraaf 4.1, en het vertelde me meteen dat dit geen geraden wachtwoord was.

Met die tijdstempel dook ik het access log in. Het IP-adres van de aanvaller verscheen voor het eerst twee minuten eerder, op een gewone artikelpagina, met als verwijzende pagina https://www.google.com/. Een zoekopdracht op Google naar die pagina maakte duidelijk waarom: de pagina was geïndexeerd terwijl de debug-uitvoer van Joomla erop zichtbaar was. Ze waren niet op zoek geweest naar mijn gebruikersgroep. Ze waren op zoek geweest naar sites die debug-gegevens lekten, en Google had hen die van mij aangereikt.

De hoofdoorzaak lag bij mijzelf en was heel alledaags. Een JUG-bijeenkomst was afgelast, de nieuwsbrief over de aflasting, liep halverwege vast door SMTP-authenticatiefouten, en ik schakelde de debugmodus in om dit te onderzoeken. De echte fout bleek bij de DNS te liggen: twee mail.-records met verschillende IP-adressen, waarvan er één verkeerd was. Ik repareerde het, verstuurde de nieuwsbrief, plaatste het bericht over de afgelasting en vergat de debugmodus weer uit te schakelen. Precies de hierboven beschreven tijdelijke wijziging die nooit ongedaan was gemaakt.

Het logboek toonde ook een tweede, niet-gerelateerde bezoeker die later die avond via een Facebook-link binnenkwam, die het bestaande sec.php-bestand gebruikte om configuration.php te lezen en er vandoor ging met de database- en SMTP-inloggegevens. Eén inbraak, twee aanvallers, en de reden waarom paragraaf 3.1 erop aandringt dat je ervan uitgaat dat alles in dat bestand openbaar is.

Wat het heeft opgeleverd

Omdat het erop leek dat de Joomla core software hierbij betrokken was, heb ik alles doorgestuurd naar het Joomla Security Strike Team. Zij bevestigden dat er daadwerkelijk sprake was van een probleem, en het kreeg de aanduiding CVE-2022-27912, advisory [20221001] Core - Disclosure of critical information in debug mode, van toepassing op Joomla 4.0.0 tot 4.2.3 en opgelost in Joomla 4.2.4 op 25 oktober 2022. Pas toen heb ik alle inloggegevens vernieuwd en een schone back-up teruggezet. Een Duitse collega wiens Joomla 4-site maanden eerder was gehackt, bleek ook de debugmodus ingeschakeld te hebben gehad.

Het CVE-record is toegekend door het Joomla-project en vermeldt mij als de ontdekker van het issue. Ik vermeld dit hier om slechts één reden, en dat is juist de kern van paragraaf 9.5, niet om ermee te pronken: ik ga niet op jacht naar kwetsbaarheden en ik heb deze niet met opzet gevonden. Ik werd gehackt, heb het bewijsmateriaal bewaard, opgeschreven wat ik zag en het gerapporteerd aan mensen die het verder konden analyseren. Dat was genoeg. Als je eigen onderzoek ooit naar een issue in de Joomla core wijst, dan zijn je aantekeningen meer waard dan je denkt.

Twee dingen zijn het vermelden waard. Dat specifieke lek is gedicht: in de huidige versie van Joomla controleert de debug-AJAX-handler zowel de autorisatie van de gebruiker als een geldig sessietoken, zodat deze niet meer op dezelfde manier kan worden aangeroepen als in 2022. Maar de gewoonte die mij blootstelde, is nog steeds voor iedereen beschikbaar, omdat de optie Allowed Groups van de debug-plugin leeg is, wat betekent dat bij ingeschakelde debug-modus de uitvoer wordt getoond aan iedereen die kijkt. Als je op een live site moet debuggen, beperk die optie dan eerst tot Super Users en schakel debug uit als je klaar bent.

Ik heb een volledige verslag in het Joomla Community Magazine geschreven, inclusief de logregels en de tijdstippen: How my new Joomla 4 website got hacked. De Joomla User Group bijeenkomst die de hele situatie in gang zette, die we hadden afgezegd omdat er behalve de spreker precies één persoon zich had aangemeld, werd verplaatst naar 2 november. Die bijeenkomst trok een volle zaal. Naast een lezing over Joomla migraties was er een korte presentatie van ondergetekende met de titel "Hoe de JUG-website werd gehackt".

Naar boven

5. Zoek wat ze hebben achtergelaten

Aanvallers plaatsen meer dan een weg terug. De eerste webshell vinden is niet het einde van de klus; de derde vinden wel. Werk elke locatie hieronder af, ook als je denkt dat je hem al gevonden hebt.

5.1 Gewijzigde core-bestanden

Joomla core-bestanden zijn volstrekt voorspelbaar, en dus makkelijk te controleren: download precies dezelfde Joomla-versie van de officiele site, pak hem lokaal uit en vergelijk met je site.

diff -rq /pad/naar/schone-joomla-6.1.1 /pad/naar/site \
  | grep -vE "configuration.php|images/|tmp/|cache/|administrator/logs/"

Elke "differ"-regel voor een core-bestand is een bestand om te inspecteren. Elke "Only in /pad/naar/site"-regel in een core-map is een bestand dat helemaal niet bij Joomla hoort - je belangrijkste verdachtenlijst.

Joomla kan zichzelf ook repareren. In Systeem → Update → Joomla biedt Joomla, als er geen update beschikbaar is, de knop Joomla core-bestanden opnieuw installeren, die alle core-bestanden opnieuw over de bestaande installatie schrijft. Begrijp de beperking: het overschrijft core-bestanden, het verwijdert geen bestanden die Joomla nooit heeft meegeleverd. Het repareert een geinjecteerde index.php; het doet niets aan een webshell die media/vendor/x9k2.php heet.

5.2 Geuploade webshells

Een webshell is een PHP-bestand dat de aanvaller via de browser een commando-interface of uploadscherm geeft. Meestal staat hij ergens waar uploads binnenkomen. Zoek naar uitvoerbare code in mappen die alleen data horen te bevatten:

find images/ tmp/ cache/ media/ administrator/logs/ \
     -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.php[57]" \)

Een normale Joomla-site heeft daar vrijwel geen PHP-bestanden, en de bestanden die er staan zijn lege beschermbestandjes. Al het andere verdient een goede blik.

Grep daarna de hele site op de functies waar malware op leunt:

grep -rEl "eval\s*\(|base64_decode|gzinflate|str_rot13|assert\s*\(|preg_replace\s*\(.*/e|\\\$_(POST|GET|REQUEST|COOKIE)\s*\[[^]]*\]\s*\(" \
  --include="*.php" .

Dit levert vals-positieven op - legitieme libraries gebruiken base64_decode om prima redenen - dus lees de resultaten, verwijder ze niet blind. Waar je naar zoekt is code die met opzet onleesbaar is: een enorme regel, geneste codering, variabelenamen als $_0x4a2, of een lange base64-brok die rechtstreeks in eval() verdwijnt.

Vergeet de bestanden niet die zich verstoppen in hun naam. Een bestand .well-known/x.php, images/logo.png.php of gewoon wp-config.php op een Joomla-site is geen vergissing.

Beide zoekopdrachten hierboven kijken naar de bestandsextensie, en daar zit hun zwakke plek. Lees paragraaf 5.8 voordat je besluit dat de map schoon is, want de payload die je zoekt heet misschien helemaal geen .php.

5.3 Geinjecteerde inhoud in de database

De bestanden opschonen en de database vergeten is een veelgemaakte halve reparatie. Joomla bewaart genoeg inhoud die als HTML wordt weergegeven, en geinjecteerde scripts verstoppen zich daar comfortabel:

SELECT id, title FROM `#__content`
 WHERE introtext LIKE '%<script%' OR fulltext LIKE '%<script%'
    OR introtext LIKE '%<iframe%' OR fulltext LIKE '%<iframe%';

SELECT id, title, module FROM `#__modules`
 WHERE content LIKE '%<script%' OR content LIKE '%<iframe%';

Controleer vooral deze plekken:

  • Aangepaste modules (mod_custom): de klassieke schuilplaats, omdat de module op elke pagina kan staan en niemand de inhoud leest.
  • Artikelen: verborgen <div style="display:none">-blokken vol links, meestal in oudere gearchiveerde artikelen.
  • Templatestijl-parameters in #__template_styles: veel templates bieden een veld voor "custom code" of "analytics code" dat in elke paginakop wordt gezet.
  • Categorie- en menu-item-beschrijvingen, die niemand ooit nakijkt.

Bijbehorende instelling om te controleren: Systeem → Globale configuratie → Tekstfilters. Super Users staan standaard op Geen filter, en precies daarom kan inhoud die via een gekaapt Super User-account is opgeslagen ruwe <script>-tags bevatten.

5.4 Malafide extensies en plugins

Een serieuze achterdeur zit niet in een willekeurig bestand. Hij registreert zichzelf als Joomla-plugin, zodat Joomla hem bij elk verzoek laadt, en hij overleeft een core-herinstallatie.

Ga naar Systeem → Beheren → Extensies en sorteer op type en naam. Alles wat je niet kunt plaatsen is verdacht. Controleer daarna de database direct, want een goed gemaakte achterdeurplugin verstopt zich voor de extensiebeheerder door daar niet netjes te verschijnen:

SELECT extension_id, name, type, element, folder, enabled
FROM `#__extensions`
WHERE type = 'plugin'
ORDER BY folder, element;

Vergelijk die lijst met de mappen die daadwerkelijk onder plugins/ staan. Een map zonder databaserij en een databaserij zonder map zijn allebei het onderzoeken waard. Gebruik Systeem → Beheren → Ontdekken hier voorzichtig: het vindt niet-geregistreerde extensies op schijf, maar het is bedoeld om ze te installeren, dus gebruik het om te kijken, niet om te klikken.

5.5 Geplande taken en cron

Voor een blijvende ingang is geen webverzoek nodig. Controleer beide lagen:

  • Joomla taakplanner (Systeem → Beheren → Geplande taken, tabel #__scheduler_tasks): zoek taken die jij niet hebt gemaakt, zeker taken die heel vaak draaien.
  • Cronjobs op de server: draai crontab -l voor je account en kijk op de cron-pagina van het controlepaneel. Een cronregel die een extern bestand downloadt en uitvoert besmet de site minuten na elke opschoning opnieuw, en dat is de klassieke verklaring voor "ik had het schoongemaakt en het was 's nachts terug".

5.6 Configuratie- en toegangsbestanden

Een paar bestanden worden bij elk verzoek geladen, en dat maakt ze aantrekkelijke doelen:

BestandWaar je op let
index.php (site en administrator) Code voor of na de oorspronkelijke inhoud, of een toegevoegde include.
configuration.php Extra code buiten de class, gewijzigde log_path of tmp_path, een veranderde $secret.
.htaccess Voorwaardelijke doorverwijzingen op basis van HTTP_REFERER of HTTP_USER_AGENT, of een ErrorDocument die naar een PHP-bestand wijst.
.user.ini / php.ini Een auto_prepend_file-regel. Die laadt de code van de aanvaller voor elk PHP-script, zonder een enkel Joomla-bestand aan te raken.
Template index.php Geinjecteerde scripttags in de template-uitvoer. Kijk ook in de html/ override-map.

De auto_prepend_file-truc verdient extra aandacht, omdat hij onzichtbaar is voor een bestandsvergelijking van Joomla zelf. Alles in de Joomla-map kan perfect schoon zijn terwijl elk verzoek nog steeds vreemde code draait.

5.7 De doorverwijzing die alleen voor vreemden werkt

Als bezoekers doorverwijzingen melden, maar jij er nooit een ziet, controleert de malware eerst wie je bent voordat deze in actie komt. Deze wordt doorgaans alleen geactiveerd wanneer de verwijzer een zoekmachine is, alleen op mobiele user agents, slechts één keer per IP-adres (opgeslagen in een cookie) en nooit wanneer er een ingelogde Joomla-sessiecookie aanwezig is.

Test het op de manier waarvan de aanvaller verwacht dat je het niet zult doen: gebruik een incognitovenster, een mobiel apparaat met mobiele data en een verzoek met een vervalste referrer.

curl -sI -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)" \
     -e "https://www.google.com/" https://example.test/ | head -20

Een Location:-header die ergens onverwachts heen wijst bevestigt het in een commando.

5.8 De payload die geen PHP-bestand is

Elke zoekopdracht in paragraaf 5.2 kijkt naar een PHP-extensie. Precies die aanname zijn deze aanvallen gebouwd om te omzeilen, en vaak niet uit slimmigheid maar uit noodzaak: het lek dat de aanvaller gebruikt laat hem misschien alleen een onschuldig bestandstype wegschrijven.

In juli 2026 hielp ik een klant van wie de oude Joomla 3-site was gehackt via CVE-2026-49049, een lek in de toegangscontrole van de Helix3-plugin van JoomShaper, gepubliceerd op 29 juni 2026 en beoordeeld met 7.5. De omschrijving legt de hele aanval in een zin uit: de blootgestelde Ajax-handlertaak laat ongeauthenticeerde aanvallers willekeurige bestanden verwijderen, willekeurige JSON-bestanden wegschrijven en templateparameters aanpassen.

Alleen JSON kunnen wegschrijven klinkt als een beperkt probleem. Dat is het niet. De bots schreven complete PHP-webshells in .json-bestanden, wat op zichzelf niets oplevert, want een bezoeker kan geen bestand op jouw server hernoemen en Apache serveert .json als platte tekst. De bots probeerden daarnaast .htaccess-bestanden neer te zetten met hierin:

<FilesMatch "\.json$">
  SetHandler application/x-httpd-php
</FilesMatch>

Dat kleine blokje is de hele aanval. De aanvaller kan het bestand niet hernoemen, dus verandert hij in plaats daarvan de regel die bepaalt wat PHP is. SetHandler vertelt Apache welke handler een passend verzoek afhandelt, en de meegeleverde htaccess.txt van Joomla zegt niets over handlers. Zodra dat blokje er staat, is images/data.json geen download meer maar een webshell, die draait als jouw PHP-gebruiker met toegang tot je bestanden en je database.

Dat is de algemene les, en die is meer waard dan deze ene CVE: een schrijfmogelijkheid die beperkt is tot een onschuldig bestandstype is niet onschuldig als de aanvaller ook kan veranderen wat dat bestandstype betekent. Lees je een melding waarin staat "kan alleen JSON wegschrijven" of "kan alleen tekstbestanden wegschrijven", schat het risico dan niet laag in op grond van de extensie alleen.

Waarom het misging, en waarom dat niet geruststellend is

Op de server van die klant is nooit iets uitgevoerd. De site draait op nginx, en nginx leest helemaal geen .htaccess-bestanden. De rootkits stonden als dode tekst op schijf terwijl de bots bleven proberen. De aanvallers hadden zich voorbereid op Apache en kwamen ergens anders terecht.

De les is niet "nginx is veilig". De les is dat de payload alleen draait als er echt een handler geregistreerd staat onder de naam die de aanvaller gokte. application/x-httpd-php is de klassieke naam van mod_php. Een moderne PHP-FPM-opstelling routeert PHP meestal via een proxy, met een handler als proxy:unix:/run/php-fpm.sock|fcgi://localhost, en cPanel met EasyApache gebruikt namen met versienummer zoals application/x-httpd-ea-php81. Gok je verkeerd, dan gebeurt er niets. Gok je goed, dan heb je code-uitvoering op afstand. Daarom zetten bots meerdere varianten in een keer neer. Of jouw server dit overleefde is een configuratiedetail, geen beveiligingsmaatregel die jij hebt gekozen.

Een tweede route: bestanden die de webserver zelf verwerkt

Later diezelfde maand schoonde ik andere klantsites op die via een ander lek waren binnengedrongen, en daar gebruikten de aanvallers een ander bestandstype om hetzelfde te bereiken. Juist daarom is het patroon de moeite waard om te leren, en niet de losse truc.

De weg naar binnen was CVE-2026-21627 in de Tassos Framework-plugin (plg_system_nrframework), beoordeeld met 9.5 kritiek op CVSS 4.0. Paragraaf 8.1 van het Focus On-artikel over Joomla beveiligen behandelt hoe die plugin daar nog geinstalleerd stond, want dat is een les op zich. Hier gaat het om wat de bots uploadden: een groot aantal .shtml-bestanden, plus een handvol .txt-bestanden.

.shtml is de klassieke extensie voor Server Side Includes. Als Apache is ingesteld om die extensie via mod_include te verwerken, en de map heeft Options +Includes, dan leest de server het bestand voordat hij het verstuurt en voert hij opdrachten uit die als HTML-commentaar geschreven zijn:

<!--#include virtual="/een/ander/bestand" -->
<!--#exec cmd="..." -->

De tweede voert een shell-commando uit. Er komt op geen enkel moment PHP aan te pas, en daarom ziet een malwarescan die alleen naar PHP kijkt helemaal niets. Op die sites gebruikten de bots hun .shtml-bestanden om .php-achterdeuren in de webroot te schrijven, wat dezelfde tweetrapsvorm is als bij de JSON-zaak: krijg eerst een willekeurig bestand binnen, gebruik daarna een servereigenschap om dat bestand iets te laten doen.

Het mislukte opnieuw, en opnieuw door nginx, maar om een andere reden die het weten waard is. nginx heeft wel een SSI-module, maar die staat in de meeste builds niet aan, en belangrijker: er bestaat helemaal geen exec cmd-opdracht. Includen en echoen kan, commando's uitvoeren niet. Apache's mod_include kan dat wel, tenzij de map op IncludesNOEXEC staat.

Joomla helpt je hier ook niet mee. De meegeleverde htaccess.txt zet Options +FollowSymlinks en Options -Indexes, en zegt niets over Includes. Zet je hoster SSI globaal aan, en sommige shared hosts doen dat nog steeds, dan erft elke map op je site dat over. Options -Includes toevoegen aan je uploadmappen kost je op een normale Joomla-site niets, want Joomla gebruikt nooit SSI.

Wat dit verandert aan je onderzoek

Ten eerste: zoek op inhoud in plaats van op extensie. Elk bestand in een datamap dat een PHP-open-tag of een SSI-opdracht bevat is een vondst, hoe het ook heet:

grep -rlE "<\?php|<!--#(exec|include)" \
  images/ tmp/ cache/ media/ administrator/logs/

Dezelfde payload duikt op als .json, .shtml, .txt, .ico, .css en .log. Malwarescanners die alleen PHP-bestanden openen missen ze allemaal, en een hostingpaneel dat meldt "geen PHP-bestanden in images" ook.

Ten tweede: behandel elk .htaccess-bestand op de site als bewijsmateriaal. Een schone Joomla 6-kern levert er precies twee: die in de webroot, hernoemd vanuit htaccess.txt, en libraries/.htaccess, die alle toegang tot die map weigert. Al het andere is neergezet door jou, je hoster, een extensie of je aanvaller:

find . -name ".htaccess" -type f

grep -rl --include=".htaccess" \
  -E "SetHandler|AddHandler|AddType|php_value|php_flag|auto_prepend_file" .

Lees elke treffer. Een gewijzigde handler, een regel AddType application/x-httpd-php .json, of een auto_prepend_file die naar een afbeelding wijst, is een achterdeur die het verwijderen van de webshell overleeft, omdat hij het volgende bestand dat de aanvaller uploadt meteen weer scherp zet. Daarom noemt paragraaf 13.5 .htaccess in een adem met plugins en cronjobs: het is een plek waar code zich kan verstoppen zonder zelf code te bevatten.

Paragraaf 8.1 behandelt de reparatie, en die is niet de voor de hand liggende. Je eigen .htaccess in de uploadmap zetten is niet genoeg als de aanvaller in diezelfde map bestanden kan schrijven.

Naar boven

6. Opschonen of opnieuw opbouwen?

Nu bepaal je hoe je bij een betrouwbare site komt. Er zijn drie routes, en de eerlijke rangschikking is niet degene waar de meeste mensen op hopen.

6.1 Een schone back-up terugzetten - het snelst

Zegt je tijdlijn (paragraaf 4.5) dat de inbraak op de 14e was en heb je een volledige back-up van de 10e, dan is die back-up terugzetten de snelste en betrouwbaarste route. Je krijgt een bekende toestand, zonder giswerk over wat je hebt gemist.

De prijs is de inhoud tussen de 10e en vandaag, die je met de hand opnieuw invoert. Dat kost echt iets, maar meestal minder dan een onvolledige opschoning.

Zet de back-up nooit over de besmette bestanden heen. Verwijder eerst alles in de webroot en zet dan terug. Anders blijft de webshell die niet in je back-up zit gewoon staan.

6.2 Opnieuw opbouwen op schone bestanden - het betrouwbaarst

Is geen enkele back-up oud genoeg, bouw dan de bestandskant helemaal opnieuw op en behoud alleen de database en je eigen inhoud. Het kost een paar uur en laat niets achter, omdat je nooit een onbekend bestand meeneemt. Paragraaf 7 beschrijft de procedure stap voor stap.

Dit is de route die ik aanraad voor elke site waar de tijdlijn onduidelijk is, en de enige zinnige route als de hack ouder is dan elke back-up die je hebt.

6.3 Ter plekke opschonen - het laatste redmiddel

Kwaadaardige code uit de bestaande installatie halen is de aantrekkelijkste optie en degene die het vaakst mislukt. Het werkt alleen als je elke wijziging van de aanvaller kunt opsommen, en daar kun je nooit helemaal zeker van zijn.

Bewaar dit voor kleine, duidelijke, vers ontdekte besmettingen met een korte en complete tijdlijn. En volg het dan alsnog op met een volledige vergelijking tegen schone bronnen.

6.4 De database is het lastige deel

Welke route je ook kiest, de database kun je meestal niet vervangen, omdat daar de inhoud in zit die je wilt houden. Hij moet worden nagelopen in plaats van herbouwd: de geinjecteerde inhoud van paragraaf 5.3, de accounts van paragraaf 3.4, de extensierijen van paragraaf 5.4 en de geplande taken van paragraaf 5.5.

Een structurele controle wordt makkelijk vergeten. Ga naar Systeem → Beheren → Database, waar Joomla controleert of je tabelstructuur overeenkomt met het schema van je versie, en repareer wat daar wordt gemeld. Het detecteert geen kwaadaardige rijen, maar het vertelt wel of de structuur zelf is aangepast of half bijgewerkt is blijven staan.

Naar boven

7. Stap voor stap opnieuw opbouwen

Dit is de procedure voor een volledige herbouw op schone bestanden. Werk op een testomgeving als dat kan, zodat het live account onaangeroerd blijft tot je er klaar voor bent.

  1. Bewaar drie dingen van de oude site, en niets anders: een databasedump, de map images/ (alleen bestanden, geen PHP), en een notitie van je instellingen uit configuration.php. Al het andere dat je meeneemt is een risico dat je aanvaardt zonder het te kennen.
  2. Leeg de webroot volledig. Niet "verwijder de verdachte bestanden" - verwijder alles. Deze stap maakt de hele aanpak betrouwbaar.
  3. Installeer de nieuwste Joomla uit de officiele download, in de lege webroot, met een nieuwe databasegebruiker en een nieuw wachtwoord.
  4. Importeer de nagekeken databasedump in de nieuwe installatie, nadat je de geinjecteerde inhoud en de malafide accounts hebt verwijderd.
  5. Installeer je extensies opnieuw vanaf de originele bron. Download er elk rechtstreeks bij de ontwikkelaar. Hergebruik nooit de extensiebestanden van de gehackte site, en gebruik dit moment om de extensies over te slaan die je eigenlijk niet nodig had.
  6. Installeer de template opnieuw. Downloaden, en daarna je eigen overrides terugzetten vanuit een bron die je vertrouwt - je Git-repository, je lokale kopie, of door ze opnieuw te maken.
  7. Zet de afbeeldingen terug, maar gefilterd: alleen echte media-extensies, en geen enkele PHP.
    rsync -av --include="*/" \
          --include="*.jpg" --include="*.jpeg" --include="*.png" \
          --include="*.gif" --include="*.webp" --include="*.avif" \
          --include="*.pdf" --exclude="*" \
          oude-site/images/ nieuwe-site/images/
    Pas op met SVG-bestanden: dat is XML en kan scripts bevatten. Heb je ze nodig, bekijk ze dan eerst.
  8. Stel een nieuwe site secret in in configuration.php (een verse installatie genereert er al een) en configureer de site opnieuw vanuit de globale configuratie.
  9. Reset elk wachtwoord en verplicht een wachtwoordwijziging voor alle gebruikers. In Gebruikers → Beheren selecteer je alle accounts en zet je Herinstellen wachtwoord verplichten in een batch actie.
  10. Verplicht multi-factor authenticatie voor elke groep die de beheerkant kan bereiken, via Gebruikers → Beheren → Opties. Was een gestolen wachtwoord de oorzaak, dan is dit de instelling die de herhaling voorkomt.
  11. Werk alles bij en controleer de versies nog een keer. Door op een verouderde uitbreiding voort te bouwen, wordt het oorspronkelijke gat exact nagebouwd.
  12. Vervang de inloggegevens een tweede keer. Dit verrast mensen, dus het is het waard om expliciet te zijn: de wachtwoorden die je in paragraaf 3.1 veranderde, veranderde je terwijl de achterdeur nog draaide. Alles wat de aanvaller op dat moment kon lezen - het nieuwe databasewachtwoord in configuration.php, een nieuw SMTP-wachtwoord, een token dat je in een formulier plakte - heeft hij mogelijk al. Pas nu, op een omgeving die je zelf uit schone onderdelen hebt gebouwd, is het vervangen echt definitief. Verander ze opnieuw.
Naar boven

8. Breng de site terug online

8.1 Blokkeer PHP waar het niet hoort

Vrijwel elke webshell uit paragraaf 5.2 leunt op een aanname: dat een PHP-bestand in een uploadmap ook echt wordt uitgevoerd als iemand het opvraagt. Die aanname kun je wegnemen, en het is het waardevolste dat je tijdens de herbouw kunt toevoegen.

Joomla doet dit niet voor je. De meegeleverde htaccess.txt blokkeert mapoverzichten, zet nosniff, en stopt scripts in geuploade SVG-bestanden, maar bevat geen regel tegen het draaien van PHP in images/. Die voeg je zelf toe, in een .htaccess-bestand in de uploadmap:

<FilesMatch "\.(?i:php|phtml|php[0-9]|phar|inc)$">
  Require all denied
</FilesMatch>

Op nginx staat het equivalent in het serverblok, omdat nginx geen .htaccess-bestanden leest:

location ^~ /images/ {
    location ~ \.php$ { return 403; }
}

Pas het toe op elke map die uploads ontvangt en nooit code hoeft te draaien: images/, je mediamappen, en elke uploadmap van een formulierextensie. Test daarna of je site het nog doet en of een testbestand echt geweigerd wordt, en verwijder dat testbestand daarna.

Dit maakt uploads op zichzelf niet veilig - de aanvaller kan het bestand nog steeds neerzetten - maar het maakt van een werkende achterdeur een dood bestand op schijf, en dat scheelt enorm.

Zet de regel buiten bereik van de aanvaller

In het advies hierboven zit een gat, en het is beter om dat hardop te zeggen dan het twee keer te ontdekken. De regel staat in een .htaccess-bestand, in een map waar de aanvaller al in kan schrijven. Hij kan jouw bestand overschrijven, of zijn eigen versie in een submap zetten, die Apache als laatste leest en dus laat winnen. Paragraaf 5.8 laat de variant zien die in de praktijk gebruikt wordt, waarbij het blokje van de aanvaller PHP weer aanzet voor .json-bestanden.

De reparatie is om de beslissing uit de webroot te halen en in de serverconfiguratie te zetten, waar niets wat een uploadformulier aanraakt eroverheen kan. In een Apache virtual host:

<Directory /var/www/example.test/images>
    AllowOverride None
    Options -Includes -ExecCGI
    <FilesMatch "\.(?i:php|phtml|php[0-9]|phar|inc|shtml)$">
        Require all denied
    </FilesMatch>
</Directory>

Options -Includes schakelt Server Side Includes uit, de tweede route uit paragraaf 5.8: een .shtml-bestand dat Apache verwerkt en uitvoert zonder dat er PHP aan te pas komt. Joomla gebruikt nooit SSI, dus je verliest er niets mee.

AllowOverride None is de regel die het echte werk doet, en het is goed om te weten waarom. SetHandler, AddHandler en AddType horen allemaal bij de FileInfo-groep van Apache, dus een .htaccess-bestand kan alleen veranderen welke handler een bestand draait als AllowOverride ook FileInfo bevat. Shared hosting levert bijna altijd AllowOverride All, en die bevat het. Zet het uit voor de mappen die alleen uploads bevatten, en het blokje van de aanvaller wordt een stuk commentaar. Omdat de instelling wordt overgeerfd, zijn de submappen meteen ook gedekt.

Let op: AllowOverride None schakelt ook je eigen .htaccess in die map uit, en daarom verhuist de FilesMatch-regel mee naar hetzelfde <Directory>-blok. Laat de .htaccess in de map er toch staan als tweede laag, voor de dag dat de site verhuist naar een hoster die dit niet heeft ingesteld.

Kun je de serverconfiguratie niet zelf aanpassen, vraag het dan aan je hoster. Het is aan hun kant een kleine wijziging, en het is het verschil tussen een verdediging die jij beheert en een verdediging die je aanvaller kan bewerken.

8.2 Test voordat je de knop omzet

Loop een korte checklist af voordat de site weer publiek is:

  • De homepage en de belangrijkste templates renderen goed, uitgelogd.
  • Test met een zoekmachine-referrer en een mobiele user agent (paragraaf 5.7). Geen onverwachte doorverwijzing.
  • Bekijk de paginabron van een paar pagina's en zoek op script- en iframe-tags die je niet herkent.
  • Formulieren werken en uitgaande mail komt aan. Controleer ook de mailwachtrij: een wachtrij vol berichten die jij niet hebt verstuurd betekent dat er nog iets verstuurt.
  • Scan de site met een externe malwarescanner, en laat een tweede het bevestigen. Behandel ze allebei als detectiemiddel, niet als bewijs: signature-scanners missen standaard aangepaste of net gewijzigde malware, dus een schoon rapport is bemoedigend, niet doorslaggevend.
  • Geen onbekende Super Users, geen onbekende plugins, geen onbekende geplande taken.
  • Een test-PHP-bestand in images/ geeft 403 en wordt niet uitgevoerd (paragraaf 8.1).

8.3 Openstellen en in de gaten houden

Haal de site uit de offline-modus en blijf hem daarna minstens een maand volgen. Herbesmetting gebeurt bijna altijd snel, omdat de aanvaller geautomatiseerde scripts heeft die hun oude slachtoffers controleren.

Richt de bewaking zo in dat hij niet afhangt van jouw geheugen:

  • Zet het Gebruikersactie log aan, nu met ip_logging en API-logging ingeschakeld.
  • Zet de plugin System - Log aan met log_username, en lees error.php wekelijks.
  • Zet de plugin Task - Update Notification aan met de taakplanner, zodat je updates per e-mail hoort.
  • Koppel Google Search Console en zet de e-mailmeldingen aan.
  • Voeg een externe uptime- en inhoudsmonitor toe die waarschuwt als de homepage onverwacht verandert.
  • Maak nu een verse back-up - dit is je nieuwe schone referentiepunt - en bewaar vanaf hier meerdere generaties.

8.4 Als het terugkomt

Een herinfectie binnen enkele dagen betekent een van de volgende drie dingen, in volgorde van waarschijnlijkheid: je hebt een achterdeur over het hoofd gezien, de oorspronkelijke kwetsbaarheid is nog steeds aanwezig, of de inbreuk vindt helemaal niet plaats op je site, maar op de server, het configuratiescherm of de computer waarmee je de site beheert.

Herhaal niet zomaar dezelfde opschoonactie. Ga terug naar de tijdlijn en betrek deze keer ook de lagen buiten Joomla mee: scan het werkstation van de beheerder op programma’s die informatie stelen, controleer op onbekende SSH-sleutels en vraag je host om het account vanuit hun kant te bekijken.

Naar boven

9. Informeer wie het moet weten

9.1 Gebruikers en klanten

Als er accounts op de site bestonden, breng de accounthouders dan op de hoogte. Vertel wat er is gebeurd, om welke gegevens het ging, wat je hebt gedaan en wat je hen adviseert te doen. Wat in de praktijk neerkomt op: wijzig dit wachtwoord en wijzig het ook overal waar je het elders hebt hergebruikt.

Duidelijke, tijdige en feitelijke communicatie kost je veel minder reputatieschade dan een ontdekte doofpotaffaire. Mensen vergeven een incident. Ze vergeven het echter niet als ze in het ongewisse worden gehouden over hun eigen gegevens.

9.2 Persoonsgegevens en de juridische klok

Zijn er mogelijk persoonsgegevens ingezien, dan is dit niet alleen een technisch incident. Onder de AVG moet een datalek zonder onredelijke vertraging gemeld worden bij je toezichthouder en, waar mogelijk, binnen 72 uur nadat je ervan op de hoogte raakte. Levert het lek waarschijnlijk een hoog risico op voor de betrokkenen, dan moet je hen ook rechtstreeks informeren.

In Nederland is de toezichthouder de Autoriteit Persoonsgegevens; elk EU-land heeft een equivalent. Let op de trigger: de klok start zodra je op de hoogte raakt van het lek, niet als je het onderzoek afrondt. Twijfel je of persoonsgegevens bereikbaar waren, vraag dan snel advies in plaats van te wachten op zekerheid.

Praktisch gevolg voor de fasen hierboven: je bewijskopie uit paragraaf 2.2 en je tijdlijn uit paragraaf 4.5 zijn precies de documentatie waar dit proces om vraagt. Dat is een tweede goede reden om ze niet over te slaan.

Afhankelijk van het land en de ernst kan aangifte bij de politie ook een optie of een verwachting zijn. Het leidt zelden tot een aanhouding, maar het levert een officiele registratie op, en verzekeraars en zakelijke klanten vragen er soms om. Vraag je toezichthouder of je juridisch adviseur wat in jouw geval geldt.

9.3 Betaalgegevens

Als de website op enigerlei wijze kaartgegevens heeft verwerkt, neem dan onmiddellijk contact op met je betalingsprovider en volg diens instructies op. Ga hier niet zelf op onderzoek uit; de contractuele verplichtingen op dit gebied zijn strikt en specifiek.

9.4 Zoekmachines

Is de site echt schoon, vraag dan een beoordeling aan. Open in Google Search Console het rapport Beveiligingsproblemen en vraag een beoordeling aan; doe dat pas na de opschoning, want een mislukte beoordeling kost je extra wachttijd. Controleer het rapport Pagina's op geindexeerde spam-URL's en laat die 404 of 410 teruggeven zodat ze uit de index vallen.

Het duurt weken, soms zelfs maanden, voordat de zoekresultaten weer op het oude niveau zijn. Die vertraging, en niet de schoonmaak zelf, is meestal het duurste onderdeel van een hack.

9.5 Het Joomla Security Strike Team

Er is nog één melding die je in overweging moet nemen, en dat is degene waar veel mensen nooit aan denken. Als uit je onderzoek blijkt dat de aanvaller gebruik heeft gemaakt van de Joomla core in plaats van een extensie, een zwak wachtwoord of je eigen aangepaste code, meld dit dan aan het Joomla Security Strike Team (JSST). Dit zijn de vrijwilligers die kwetsbaarheidsmeldingen voor het CMS afhandelen, en dankzij hen bestaan er beveiligingsupdates voor de kern.

Meld het via het officiële kanaal dat wordt beschreven op de pagina voor het melden van beveiligingsproblemen in Joomla, per e-mail of via het JSST-contactformulier, in plaats van in een forumthread, een chatkanaal of een bericht op sociale media. Een openbaar besproken, nog niet gepatcht kernprobleem is een geschenk voor elke andere aanvaller en brengt elke Joomla-site in gevaar totdat er een release is uitgebracht. Het team verbindt zich ertoe een melding binnen 24 uur te bevestigen, binnen 7 dagen verder te reageren en het probleem binnen 21 dagen op te lossen of uit te leggen waarom dat niet lukt.

Wat een melding nuttig maakt, is precies het materiaal dat je al hebt verzameld: de tijdlijn uit paragraaf 4.5, de relevante regels uit het toegangslogboek die de geslaagde verzoeken weergeven, de lijst met gewijzigde bestanden en de versies die je gebruikte. Bied hen toegang tot de bewaarde kopie aan als ze daarom vragen. Je hoeft de kwetsbaarheid niet zelf te bewijzen of correct te benoemen, beschrijf wat je hebt waargenomen en laat hen het analyseren

Dit is de moeite waard, ook al kost het je tijd midden in een zware week. Als de core inderdaad de toegangspunt was, ben je vrijwel zeker niet de enige getroffen site, en jouw bewijs kan ervoor zorgen dat een onverklaarbaar patroon van gehackte sites wordt omgezet in een patch die alle anderen beschermt. Dat is geen hypothetisch scenario: het materiaal dat ik aan het JSST heb overhandigd nadat mijn eigen site was gehackt (paragraaf 4.6) werd drie weken later CVE-2022-27912 en een beveiligingsupdate voor de Joomla core.

9.6 Meld terug

Neem ten slotte opnieuw contact op met iedereen die je over het incident heeft benaderd en laat hen weten dat het is opgelost: je hostingprovider, de beveiligingsorganisatie of de persoon die het heeft gemeld (paragraaf 2.3), en iedereen die een abuse-melding heeft ingediend. Vertel wat je hebt ontdekt, wat je hebt gedaan en dat de site opnieuw is opgebouwd of schoongemaakt.

Sla deze stap niet over, ook al is het zware werk achter de rug. Een lopende melding van misbruik wordt niet vanzelf gesloten, en een onbeantwoorde melding kan je nog weken nadat de site volledig is opgeschoond je account of domein kosten. Het is ook de goedkoopste reputatie die je ooit zult kopen: hostingproviders en registries hebben vooral te maken met mensen die hen negeren, en ze onthouden degenen die wel terugkoppelen.

Naar boven

10. Onder de motorkap (ontwikkelaarsblik)

10.1 Waar een aanvaller zich verstopt in de database

TabelWat je na een hack controleert
#__users Nieuwe accounts, gewijzigde e-mailadressen, omgezette block-vlaggen, vreemde registerDate-waarden.
#__user_usergroup_map Bestaande accounts die stilletjes aan de groep Super Users zijn toegevoegd.
#__user_keys Onthoud-mij-tokens. Gooi ze allemaal weg.
#__user_profiles API-tokens staan hier onder de sleutelprefix joomlatoken. Verwijder tokens die jij niet hebt uitgegeven.
#__user_mfa MFA-methodes. Een aanvaller die zijn eigen apparaat heeft ingeschreven houdt toegang na een wachtwoordreset.
#__session Actieve sessies. Leegmaken.
#__extensions Malafide pluginrijen, en gewijzigde enabled-vlaggen op beveiligingsrelevante plugins.
#__scheduler_tasks Taken die niemand heeft aangemaakt.
#__modules Aangepaste modules met scripts, gepubliceerd op alle pagina's.
#__template_styles Custom-code-parameters die in elke pagina worden geinjecteerd.
#__action_logs Je bewijsmateriaal. Exporteer het voordat iets het opschoont.
#__update_sites Een gewijzigde update-URL maakt van je updatesysteem een bezorgkanaal.

Die laatste regel verdient een tweede blik. Wijst #__update_sites ergens heen dat niet de officiele Joomla- of ontwikkelaars-URL is, dan komt elke toekomstige "update" die je installeert van de aanvaller.

10.2 Een praktische triage-gereedschapskist

Vier commando's die de meeste eerste vragen beantwoorden:

# 1. What changed recently?
find . -type f -newermt "2026-07-01" ! -path "./cache/*" ! -path "./tmp/*"

# 2. PHP where PHP does not belong
find images tmp cache media -type f -name "*.ph*"

# 3. Obfuscation markers
grep -rn --include="*.php" -E "eval\s*\(|base64_decode|gzinflate|\\\$GLOBALS\[" . | head -50

# 4. Files that are writable by everyone
find . -type f -perm -o+w

Een gehackte site ontvangt niet alleen verkeer, hij maakt ook zelf verbindingen: spam versturen, extra payloads ophalen, zich melden bij een command-and-control-host, of minen. Heb je shelltoegang terwijl het incident loopt, kijk dan wat de server op dit moment doet:

ss -plant          # luisterende sockets en actieve verbindingen
ps auxf            # procesboom, inclusief wat PHP heeft gestart
lsof -nP           # open bestanden en sockets per proces
mailq              # mail die klaarstaat om verstuurd te worden

Bewaar de uitvoer voordat je iets afschiet. Een draaiend proces is bewijs dat verdwijnt op het moment dat je het stopt, en de verbinding die het openhoudt wijst vaak sneller de infrastructuur achter de aanval aan dan welk bestand op schijf dan ook.

Staat de site in versiebeheer, dan heb je een veel beter gereedschap dan al deze:

git status --porcelain
git diff --stat

Elke onverwachte regel is een gewijzigd of toegevoegd bestand, zonder giswerk. Dit is het sterkste praktische argument om ook kleine Joomla-sites in Git te houden: na een incident wordt "wat is er veranderd" een vraag van een seconde in plaats van een onderzoek van twee uur.

10.3 Waarom de Site Secret belangrijk is

Joomla bouwt de naam van de sessiecookie met md5($secret . $session_name), waarbij $session_name terugvalt op de naam van de applicatieklasse. De token-plugins controleren tokens met hash_hmac($algorithm, $rawToken, $siteSecret). Een enkele wijziging aan $secret beeindigt dus alle sessies en maakt alle API-tokens ongeldig, precies wat je wilt in de eerste minuten van een incident.

Het is goed om de grenzen te benoemen, zodat niemand er te veel op leunt: wachtwoorden zijn bcrypt-hashes en blijven ongemoeid, ingeschreven MFA-methodes in #__user_mfa blijven werken, en onthoud-mij-tokens in #__user_keys worden per user agent bewaard en hebben hun eigen opruiming nodig.

10.4 Een opgeschoonde site verifieren

Vertrouw hier niet op je eigen ogen. Bouw een checksumlijst van een schone Joomla van dezelfde versie en vergelijk:

cd /pad/naar/schone-joomla && find . -type f -name "*.php" \
  -exec sha256sum {} \; | sort -k2 > /tmp/clean.sha256

cd /pad/naar/site && find . -type f -name "*.php" \
  -exec sha256sum {} \; | sort -k2 > /tmp/site.sha256

diff /tmp/clean.sha256 /tmp/site.sha256

Alles wat verschilt is ofwel een eigen aanpassing die je kunt benoemen, ofwel iets om uit te leggen. "Ik weet niet wat dat bestand is" is geen geslaagde uitkomst.

Twee beperkingen zijn belangrijk als je hier doorlopende bewaking van maakt. Een hashlijst vangt alleen gewijzigde bestanden, en de meeste malware voegt gewoon nieuwe toe, dus vergelijk ook de bestandslijsten en niet alleen de hashes. En bewaar de referentiegegevens ergens waar het webaccount niet kan schrijven - een andere server, of opslag waar je PHP-gebruiker niet bij kan. Een checksumbestand naast de site is een checksumbestand dat de aanvaller opnieuw kan genereren.

Naar boven

11. De Web Services API na een hack

De Joomla API wordt bij een opschoning makkelijk vergeten, en het is een complete tweede voordeur naar dezelfde site.

  • Trek elk token in. Het wijzigen van $secret doet dit in een keer, omdat tokens er met HMAC tegen worden gecontroleerd. Je kunt het token ook per gebruiker resetten op het profiel.
  • Kijk wie tokens mag gebruiken. De plugin User - Joomla API Token heeft de parameter allowedUserGroups. Is die verruimd voorbij Super Users, controleer dan of jij dat hebt gedaan.
  • Controleer de authenticatieplugins. Staat API Authentication - Basic aan terwijl jij altijd alleen tokens gebruikte, vraag je dan af wie hem heeft ingeschakeld.
  • Zet API-logging aan in de opties van het gebruikersactielogboek (loggable_api), zodat het volgende incident hier ook een spoor achterlaat.
  • Zet uit wat je niet gebruikt. Heeft de site geen headless afnemer, schakel de API-plugins dan helemaal uit.

Testen of het oude token echt dood is kost een commando:

curl -i -H "X-Joomla-Token: <oud-token>" \
     https://example.test/api/index.php/v1/users

Een 401 is het antwoord dat je wilt.

Naar boven

12. SEO en metadata

Een hack is net zo goed een zoekmachineprobleem als een technisch probleem, en de schade overleeft de besmetting vaak met maanden.

Wat er meestal gebeurt: de aanvaller publiceert honderden spampagina's die onder jouw domein worden geindexeerd, injecteert verborgen links in bestaande artikelen, en serveert de Googlebot andere inhoud dan jou. Google detecteert het, markeert de site in Search Console, en kan een waarschuwing in de resultaten tonen. Posities die je jaren hebt opgebouwd zakken binnen dagen weg.

De volgorde van herstel is belangrijk:

  1. Maak de site eerst echt schoon. Een beoordelingsverzoek op een nog besmette site mislukt en kost je opnieuw wachttijd.
  2. Laat de spam-URL's 404 teruggeven of, beter, 410 Gone. Verwijs ze niet door naar je homepage; dat vertelt Google alleen dat de spampagina is verhuisd.
  3. Vraag een beoordeling aan onder Beveiligingsproblemen in Search Console.
  4. Controleer of je domein op mail- of malware-blocklists is beland, en volg elke delisting-procedure.
  5. Volg het aantal geindexeerde pagina's een paar weken tot de spam-URL's verdwenen zijn.

Er zit ook een preventieve kant aan. Meldingen van Search Console zijn gratis en bereiken je vaak eerder dan je bezoekers. Een site die niemand volgt kan maandenlang spam serveren voordat het opvalt, en hoe langer dat duurt, hoe langer het herstel duurt.

Naar boven

13. Veelgemaakte fouten en valkuilen

13.1 De malware direct verwijderen

Symptoom: je vindt een vreemd PHP-bestand, verwijdert het opgelucht, en hebt nu geen idee wanneer het is gekomen of wat het heeft aangemaakt.

Oplossing: kopieer eerst de hele site en database (paragraaf 2.2). De tijdstempels van die bestanden zijn de draad naar het toegangspunt. Eerst onderzoeken, dan verwijderen.

13.2 Een back-up terugzetten en het klaar noemen

Symptoom: de site is teruggezet, ziet er goed uit, en is binnen een week opnieuw besmet.

Oplossing: een restore draait de symptomen en de oplossing tegelijk terug - je zit weer op dezelfde kwetsbare versie. Zet terug, en werk daarna direct alles bij en verander alle inloggegevens voordat de site weer publiek gaat.

13.3 Terugzetten over de besmette bestanden heen

Symptoom: de back-up is echt schoon, de restore slaagt, en de achterdeur zit er nog.

Oplossing: een restore overschrijft alleen de bestanden die in het archief zitten. Een webshell die nooit in je back-up zat blijft onaangeroerd staan. Leeg de webroot voordat je terugzet.

13.4 De bestanden opschonen en de database negeren

Symptoom: alle bestanden komen overeen met een schone Joomla, maar bezoekers worden nog steeds doorgestuurd.

Oplossing: controleer de database (paragraaf 5.3). Een gepubliceerde aangepaste module met een enkele <script>-tag reproduceert het hele symptoom met een volstrekt schoon bestandssysteem.

13.5 De tweede achterdeur missen

Symptoom: de site is zorgvuldig schoongemaakt en is binnen dagen terug.

Oplossing: ga ervan uit dat er altijd meer dan een is. Controleer plugins, geplande taken, server-cron, .htaccess en auto_prepend_file voordat je de overwinning uitroept, niet erna.

13.6 De oude extensiebestanden hergebruiken

Symptoom: bij een herbouw worden de extensies "om tijd te besparen" van de oude site overgezet.

Oplossing: download elke extensie vers bij de ontwikkelaar. Extensiemappen meenemen is het onbekende meenemen, en het ondermijnt het hele doel van een herbouw.

13.7 Aannemen dat de site de ingang was

Symptoom: Joomla is volledig bijgewerkt, alles is schoon, en er blijven bestanden veranderen.

Oplossing: verbreed het zoekgebied. Gestolen FTP-gegevens van een besmette werkplek, een gekaapt controlepaneel, een gelekte SSH-sleutel of een buuraccount op een slecht gescheiden shared server geven precies dit beeld. Betrek je hoster erbij.

13.8 Niemand informeren

Symptoom: er stonden gebruikersgegevens in de database, en het incident wordt stilletjes opgeruimd en nooit genoemd.

Oplossing: waren persoonsgegevens bereikbaar, dan is melden een wettelijke verplichting met een klok van 72 uur, geen kwestie van voorkeur (paragraaf 9.2). Regel het parallel aan het technische werk.

Naar boven

14. Best practices

Onthoud je maar een paar dingen uit dit artikel, onthoud dan deze:

  • Eerst beperken, dan onderzoeken, dan opschonen. Draai die volgorde nooit om.
  • Kopieer de besmette site en database voordat je iets verandert. Het is je bewijs en je vangnet.
  • Vind het toegangspunt. Een opschoning zonder dat is uitstel, geen reparatie. Vraag je af wat je tijdens het troubleshooten hebt aangezet en nooit hebt uitgezet.
  • Controleer de logintijden van je eigen account, niet alleen de lijst met onbekende gebruikers. Een login die je niet kunt verklaren betekent dat ze je wachtwoord niet nodig hadden.
  • Verander alle inloggegevens, en verander de $secret in configuration.php om alle sessies en API-tokens in een keer te beeindigen.
  • Leeg de webroot voordat je terugzet of herbouwt. Schoon nooit rond onbekende bestanden heen.
  • Installeer extensies en templates opnieuw vanaf de originele bron, nooit vanaf de gehackte site.
  • Controleer de database net zo zorgvuldig als de bestanden. Aangepaste modules en templateparameters verbergen injecties goed.
  • Zoek de tweede en derde achterdeur: plugins, geplande taken, server-cron, .htaccess, auto_prepend_file.
  • Blokkeer het uitvoeren van PHP in uploadmappen, en doe dat vanuit de serverconfiguratie met AllowOverride None, niet alleen vanuit een .htaccess-bestand dat de aanvaller kan overschrijven. Joomla doet dit niet voor je, en het schakelt de meeste webshells uit.
  • Blokkeer het uitvoeren van PHP in uploadmappen. Joomla doet dit niet voor je, en het schakelt de meeste webshells uit.
  • Verplicht MFA en werk alles bij voordat de site weer publiek gaat.
  • Vervang de inloggegevens een tweede keer zodra de schone site er staat. De eerste ronde gebeurde terwijl de achterdeur meekeek.
  • Volg de site een maand lang goed, en maak een verse back-up als je nieuwe schone referentie.
  • Meld een datalek binnen 72 uur. Vertel je gebruikers gewoon wat er is gebeurd.
  • Was Joomla core de ingang, meld het dan bij het JSST in plaats van het openbaar te maken.
  • Meld terug aan je hoster en aan wie de hack meldde. Een openstaande abuse-melding kan je alsnog het account kosten.
  • Repareer daarna de oorzaak: de gemiste update, het zwakke wachtwoord, de extensie die niemand onderhoudt.
Naar boven

15. In het kort

FASE 1  BEPERKEN
        blokkeer op serverniveau (niet alleen Joomla offline-modus)
        met SSH: mv public_html public_html.hacked ; chmod 700
                 + statische index.html  (indammen EN bewaren)
        Mail versturen (mailonline) = Nee, als de site spamt
        meerdere sites in een account? behandel ze allemaal als besmet
        licht je hoster in, vraag om ruwe access logs
        gemeld door Netcraft/SIDN/je hoster? reageer meteen, er is
        een deadline en het domein zelf kan worden stilgelegd

FASE 2  BEWAREN
        volledige bestandskopie + databasedump, tijdstempels intact
        bewaar buiten de server, label BESMET, nooit terugzetten

FASE 3  ONDERZOEKEN
        Gebruikers > Gebruikersactie log  (CSV; ip_logging staat standaard UIT)
        controleer JE EIGEN logintijden, niet alleen onbekende accounts
        administrator/logs/error.php      (System - Log, mislukte logins)
        server access logs                (POST naar images/ tmp/ media/)
        grep Accepted /var/log/auth.log   (kwamen ze op de server?)
        find . -name "*.php" -mtime -14
        grep -rl "<?php" images/ tmp/ cache/ media/  (payload kan .json zijn)
        find . -name ".htaccess"       (schone Joomla 6-kern levert er precies 2)
        bouw een tijdlijn → vroegste onverklaarde gebeurtenis = inbraak
        vraag: wat heb ik aangezet en nooit uitgezet?
        verwacht meer dan een aanvaller zodra de URL gedeeld is

FASE 4  VERWIJDEREN
        gegevens: Joomla, hosting, FTP/SSH, database, SMTP, API,
                  DNS/CDN, back-upopslag, deploy-tokens
        configuration.php leesbaar? elk geheim erin is gelekt
        configuration.php: nieuwe $secret → doodt sessies + API-tokens
        TRUNCATE #__session ; TRUNCATE #__user_keys
        gebruikers: check #__user_usergroup_map op nieuwe Super Users
        bestanden: diff tegen een schone Joomla van dezelfde versie
                   Systeem > Update > Joomla > core-bestanden opnieuw
                   installeren (overschrijft core, verwijdert GEEN
                   vreemde bestanden)
        db:        #__content #__modules #__template_styles (script/iframe)
                   #__extensions #__scheduler_tasks #__update_sites
        overig:    .htaccess, .user.ini auto_prepend_file, crontab -l

FASE 5  HERSTELLEN
        webroot leegmaken, verse Joomla, verse extensies
        images terug met een extensie-allowlist, geen PHP
        PHP weigeren in uploadmappen (.htaccess / nginx location)
        AllowOverride None op uploadmappen, in de vhost, niet in .htaccess
               (voorkomt dat de aanvaller PHP heraanzet met SetHandler)
        herinstellen wachtwoord verplichten voor alle gebruikers; MFA
        alles bijwerken; test met mobiele UA + zoekmachine-referrer
        vervang alle inloggegevens OPNIEUW op de schone omgeving

FASE 6  MELDEN
        gebruikers: wat er gebeurde, welke gegevens, wat te doen
        AVG: toezichthouder binnen 72 uur
        betaalprovider bij kaartgegevens
        was core de ingang? meld bij het JSST, niet in het openbaar
        meld terug: hoster, registratie, wie het meldde
        Search Console > Beveiligingsproblemen > beoordeling aanvragen
        spam-URL's geven 404/410, nooit doorverwijzen naar home

DAARNA  ip_logging AAN, loggable_api AAN, log_username AAN
        Task - Update Notification + taakplanner
        verse back-up = nieuwe schone referentie, bewaar generaties
Naar boven

16. Samenvatting

Een gehackte Joomla-site betekent een vervelende dag, maar niet dat de site verloren is, mits je de juiste stappen in de juiste volgorde neemt:

  • Beperk voordat je opschoont. Blokkeer op serverniveau en kopieer de besmette toestand voordat je een byte verandert.
  • Vind het toegangspunt. Het actielogboek, het logboek met mislukte logins, het server access log en de tijdstempels geven samen een tijdlijn, en die tijdlijn vertelt welke back-up nog schoon is.
  • Sluit de aanvaller buiten. Vervang alle inloggegevens, verander de site secret, en leeg #__session en #__user_keys.
  • Zoek alles wat ze achterlieten. Gewijzigde core-bestanden, webshells in uploadmappen, geinjecteerde artikelen en aangepaste modules, malafide plugins, geplande taken, .htaccess-doorverwijzingen en auto_prepend_file.
  • Kies herbouwen boven opschonen als de tijdlijn onduidelijk is. Lege webroot, verse Joomla, verse extensies, nagekeken database.
  • Kom voorzichtig terug. Werk alles bij, verplicht MFA, test zoals een vreemde dat zou doen, en volg de logs een maand.
  • Meld wat de wet en het fatsoen vragen. Gebruikers, de toezichthouder binnen 72 uur bij persoonsgegevens, het JSST als Joomla core de ingang was, en een beoordelingsverzoek aan de zoekmachines zodra je echt schoon bent. Meld daarna terug aan je hoster en aan wie het meldde.

Het belangrijkste om mee te nemen is dat dit bijna allemaal geen ingewikkeld werk is. Het gaat om geduldig en systematisch controleren: één lijst met accounts, één lijst met bestanden, één lijst met databasetabellen, en de discipline om het onderzoek niet over te slaan omdat het opschonen productiever lijkt.

Als je op dit moment midden in een incident zit en de tijdlijn niet klopt (de logbestanden zijn verdwenen, de back-ups zijn allemaal te recent, of de site blijft maar terugkomen) dan is dat het moment waarop een tweede paar ervaren ogen meestal meer tijd bespaart dan het kost. Het methodisch herstellen van een gecompromitteerde Joomla-site en het vinden van de achterdeur waar iedereen aan voorbij is gelopen, is precies het soort werk dat een Joomla-specialist vaak genoeg doet om de patronen snel te herkennen.

Naar boven
Joomla-website gehackt: wat je nu moet doen
Peter Martin
Peter Martin
Joomla Specialist

Peter is Joomla specialist en Linux admin voor snelle, veilige en schaalbare websites.

Gerelateerde artikelen