Angriffe auf WordPress

Auch auf meinen Seiten waren die Angriffe erfolgreich. Dabei gehen die Angreifer ziemlich rücksichtslos mit der Web-Seite um und bemühen sich nicht, die Angriffe zu verstecken. Auf die eigentliche Funktion nehmen die Angreifer keine Rücksicht. Da ich einige Funktionen ständig nutze, fiel mir der Ausfall recht schnell auf. Wer es erst später merkt, hat den Dreck wahrscheinlich auch im Backup.

Die Veränderungen treffen nicht nur die virtuellen WordPress Host, sondern über den Apache2 kommen sie auf weitere virtuelle Hosts auf einer Maschine. Wer nebenbei noch eine Freifunk-Map oder eine Wiki auf der gleichen Maschine betreibt, muss auch dort nachschauen.

Und sie installieren dort zum Teil WordPress. Über den Angriffsweg weiß ich (noch) nichts. Über gehackte Accounts kann er nicht erfolgt sein, da eine Anmeldung nur von meinem Laptop oder Desktop aus möglich ist. Ich vermute, dass nicht nur ein Angreifer die Lücke ausgenutzt hat. Da sind unkoordiniert mehrere am Werke.

Also wie werde ich den Dreck wieder los?

Erkennen

Es werden so viele PHP-Scripte in die Verzeichnisse und Unterverzeichnisse geschrieben, sodass ein manuelles Löschen aufwändig ist. Darüber hinaus wird der Schadcode auch an die PHP-Scripte vor oder angehängt. Da hilft nur eine Neuinstallation. Inzwischen habe ich damit Routine. Aber zuerst müssen wir erkenneb, ob die Seite betroffen ist. Das ist recht einfach. Hier die Verzeichnisstruktur in wp-content. Eine typische Struktur zeigt ls -l:

$ ls -l
insgesamt 1452
-rw-r--r-- 1 www-data www-data 170771 4. Mär 02:18 3AR8lq4nIci.php
-rw-r--r-- 1 www-data www-data 170771 4. Mär 02:12 7DZL5gsN3kr.php
-rw-r--r-- 1 www-data www-data 170771 2. Mär 23:25 9KGYXgy84Ie.php
-rw-r--r-- 1 www-data www-data 126494 21. Feb 21:33 content-css.php
-rw-r--r-- 1 www-data www-data 170771 4. Mär 02:05 gUYo8T6GHqn.php
-rw-r--r-- 1 www-data www-data 87021 21. Feb 21:33 index.php
-rw-r--r-- 1 www-data www-data 170771 4. Mär 04:01 Iq2TtPAbsUX.php
drwxr-xr-x 4 www-data www-data 4096 3. Feb 10:20 languages
drwxr-xr-x 2 www-data www-data 4096 3. Feb 10:20 mu-plugins
-rw-r--r-- 1 www-data www-data 105 4. Mär 04:01 php.ini
drwxr-xr-x 14 www-data www-data 4096 5. Feb 23:25 plugins
-rw-r--r-- 1 www-data www-data 170771 4. Mär 04:00 souA1RDBmlx.php
-rw-r--r-- 1 www-data www-data 33983 21. Feb 21:33 style-css.php
drwxr-xr-x 6 www-data www-data 4096 21. Feb 21:33 themes
-rw-r--r-- 1 www-data www-data 170771 3. Mär 08:27 TrjZCHFv6qe.php
drwxr-xr-x 2 www-data www-data 4096 17. Jan 15:38 upgrade
drwxr-xr-x 17 www-data www-data 4096 17. Jan 15:38 uploads

Die Dateien mit den Zufallsfolgen als Dateiname sind ein sicheres Zeichen. Manche Dateien gibt es gleich mehrfach. Die index.php ist hier nicht das Original und style-css.php oder php.ini hat hier nichts zu suchen. Andere, gern genommene Dateinamen sind admin.php oder about.php.

Die php.ini Datei ist ein recht sicheres Zeichen, dass ein Verzeichnis betroffen ist. Im DocumentRoot könnte eine php.ini liegen. Die muss man sich anschauen.

Oft wird folgende php.ini angelegt:

safe_mode = Off
disable_functions = NONE
safe_mode_gid = OFF
open_basedir = OFF
exec = ON

Die hat unter WordPress keinen Sinn.

wp-content/uploads

Dankenswerterweise fanden sich in wp-content/uploads keine Scripte, was die Sache vereinfacht. Es ist trotzdem ratsam dies zu prüfen, denn dieses Verzeichnis brauchen wir für die Neuinstallation.

find . -name "*.php"

Und alles überflüssige zu beseitigen, insbesondere wenn der Schadcode im Backup gelandet ist.

find . -name "*.php" -exec rm {} \;

Gleiches Verfahren mit der php.ini. Es richtet erst mal keinen Schaden an, wenn alle gelöscht werden. Das Verzeichnis wp-content/upload muss sauber sein, denn wir brauchen es für den Neuaufbau der Seite.

Neuaufbau des Seite

Zuerst legen wir die betroffene Seiten still:

cd /etc/apache2/sites-enabled 
sudo a2dissite <Meine Site 1.conf>
sudo a2dissite <Meine Site 2.conf>
...
sudo systemctl reload apache2

Jetzt kann erst mal nichts mehr passieren. Der Angreifer bekommt keinen Zugriff mehr. Bitte bedenken: Es können auch virtuelle Hosts unter Apache2 betroffen sein, die nicht WordPress verwenden.

Für solche Fälle habe ich eine vorbereitete Seite für den Wartungsmodus, aber das zu beschreiben führt hier zu weit.

Zuerst brauchen wir ein frisches WordPress. Ich lade es immer nach /tmp herunter.

cd /tmp
wget https://de.wordpress.org/latest-de_DE.zip
unzip latest-de_DE.zip

Ich verschiebe die betroffenen Seiten, die bei mir z. B. unter /var/www/vhosts/example.com liegen, in ein Verzeichnis unter /var/www/unclean/example.com.

Liegt die Seite unter /var/www/html, dann muss man analog vorgehen.

Nennen wir die Web-Seite im folgenden example.com.

cd /var/www/
sudo mkdir unclean
sudo chown www-data unclean
cd vhosts
sudo -u www-data mv example.com ../unclean/
sudo mkdir example.com
sudo chown www-data example.com

Jetzt können wir WordPress in die neuen Verzeichnisse kopieren.

cd /tmp/wordpress
sudo -u www-data cp -r * /var/www/vhosts/example.com

Nun fehlen uns noch die uploads …

cd /var/www/unclean/example.com/wp-content
sudo -u www-data cp -r uploads /var/www/vhosts/example.com/wp-content

und die Datenbank.

Datenbank erstellen und übernehmen

Im ersten Schritt erstellen wir zwei SQL-Scripte. Das erste zum Anlegen einer neuen Datenbank, dass zweite zum Übernehmen der Posts, Stichworte …

Dazu nutze ich folgendes Script make_sql.sh:

 #!/bin/bash

if [ -n "$1" ] ; then
DB_New=$1
else
DB_New="wordpress"
fi

if [ -n "$2" ] ; then
DB_Old=$2
else
DB_Old="wordpress"
fi

if [ -n "$3" ] ; then
DB_PrefixNew=$3
else
DB_PrefixNew="wp_"
fi

if [ -n "$4" ] ; then
DB_PrefixOld=$4
else
DB_PrefixOld="wp_"
fi

if [ -n "$5" ] ; then
DB_User=$5
else
DB_User=wordpress
fi

DB_Pass=$(pwgen 32 1)

cat << ENDE1 > "/tmp/${DB_New}_create.sql"

drop database if exists ${DB_New} ;

create database if not exists ${DB_New} ;

create user if not exists '${DB_User}'@'localhost' identified by '${DB_Pass}';

grant all on ${DB_New}.* to '${DB_User}'@'localhost' ;

grant all on ${DB_Old}.* to '${DB_User}'@'localhost' ;

flush privileges;

ENDE1


# for TABLE in commentmeta comments links options postmeta posts term_relationships term_taxonomy termmeta terms usermeta users

# do not copy
# commentmeta comments options usermeta users
# change if you want to copy these tables


echo "use ${DB_New} ;" > "/tmp/${DB_New}_transfer.sql"

for TABLE in links postmeta posts term_relationships term_taxonomy termmeta terms
do

cat << ENDE2 >> "/tmp/${DB_New}_transfer.sql"

/* ${TABLE} */

delete from ${DB_PrefixNew}${TABLE};

insert into ${DB_PrefixNew}${TABLE}
select * from ${DB_Old}.$DB_PrefixOld${TABLE} ;
ENDE2

done

Das Script ist nicht Bullet-proofed, aber sollte mit Anpassungen funktionieren.

Die Datenbank lege ich mit folgenden Befehlen an.

./make_sql.sh <db_new> <db_old> <prefix_new> <prefix_old> <db_user>
mysql -u root -p < /tmp/<db_new>-create.sql

Jetzt wird die Seite wieder aktiviert und WordPress mit dem Password in /tmp/<db_new>-create.sql installiert.

Nun müssen wir nur noch ausgewählte Tabellen aus der alten Datenbank übernehmen:

mysql -u root -p < /tmp/<db_new>-transfer.sql

Der manuelle Feinschliff

Ab hier beginnt dann der manuelle Feinschliff Weitere Nutzer einrichten, Plugins und Themes einrichten, …

Die Plugins und Themes werden befallen sein. Das beste ist, die aktuellen Version zu installieren. Wer vom alten Server kopiert, kann wahrscheinlich gleich von vorne anfangen.

Auf die Übernahme der User habe ich verzichtet, da ich meist der einzige User bin. Es gibt zu viele Abhängigkeiten. Die Nutzer sollte am besten per Hand in der alten Reihenfolge aber neuem Password angelegt werden. Die Übernahme der Optionen führte zu einem Absturz von WordPress. Die option_id ist nicht immer gleich.

Auf die Kommentare habe ich ganz verzichtet, weil die meisten Spam waren.

Man kann natürlich auch die alte Datenbank weiter verwenden, aber wenn WordPress nicht die neuste Version war, ist dieser Weg besser.