Wer WordPress nutzt, wird sich schon Gedanken darüber gemacht haben, wie man die Sicherheit des Systems erhöhen kann. WordPress hat ein eigenes Security Team, welches die Software stetig auf Schwachstellen hin überprüft, daher gilt der Core auch als recht sicher. Allerdings gibt es auch hier ständig neue potentielle Sicherheitslücken, wie beispielsweise die neue Rest-API. Größter Angriffsvektor für Hacker bleiben jedoch nach wie vor schlampig programmierte WordPress-Plugins. Wer die Full Disclosure-Mailingliste abonniert hat, wird wissen, was ich meine: Dort tauchen ziemlich regelmäßig Schwachstellen in WordPress-Plugins auf. Wie kann man sich nun aber effektiv schützen, auch wenn man auf bestimmte Plugins angewiesen ist? Dieser Post soll eine lose Sammlung effizienter Maßnahmen sein, die die Sicherheit des Systems verbessern.

Keine Ausführungsrechte für das DocumentRoot

Letztens hat mir jemand eine WordPress-Instanz über ein anfälliges WordPress-Plugin gehackt, eine PHP-Shell hingelegt und darüber NodeJS heruntergeladen. Anschließend wurden verschiedene Hacks auf weitere Seiten aufgeführt. Wie kann man das verhindern? Unter Linux gibt es die Möglichkeit, beim Mounten von Filesystemen die Option noexec zu setzen. Diese gewirkt, dass grundsätzlich ausführbare Dateien (wie Binaries oder Skripte) nicht ausgeführt werden können. Die Option kann man einfach in der /etc/fstab mitgeben. Voraussetzung ist natürlich, dass das DocumentRoot auf einem eigenen Filesystem liegt, beispielsweise auf einem LVM-Volume. Ist das nicht so, kann man sich mit einem kleinen Trick behelfen. Im Beispiel ist /var/www/html das DocumentRoot:

$ mount -o bind /var/www/html /var/www/html
$ mount -o remount,noexec /var/www/html

Der erste Befehl mountet das Verzeichnis auf sich selbst als sogenannten Bind-Mount. Der zweite Befehl setzt für den Mount die Option noexec und verhindert damit das Ausführen von Dateien. Folgendermaßen kann das getestet werden:

$ cat <<EOF > /var/www/html/test.sh
> #!/bin/bash
> echo "Hello World"
> EOF
$ chmod 755 /var/www/html/test.sh
$ /var/www/html/test.sh
bash: /var/www/html/test.sh: Keine Berechtigung

Hinweis: Bind-Mounts werden weder unter /proc/mounts, noch in der /etc/mtab angezeigt. Man kann sie sich aber mit findmnt anzeigen lassen.

PHP-User ohne Schreibrechte

Der User unter dem PHP ausgeführt wird, hat meist Schreibrechte auf die gesamte WordPress-Applikation. Das heißt, ein Angreifer, kann nach einem erfolgreichen Eindringen seine Sachen irgendwo verstecken, wo keiner nachsieht. Zusätzlich kann er die Applikation auch verändern, und somit ein manuelles Aufspüren der Aktivitäten erheblich erschweren. Um das zu verhindern, kann man dem PHP-User nur dort Schreibrechte geben, wo er sie auch wirklich benötigt (Upload-Verzeichnis, temporäre Ordner). Damit verliert man allerdings auch die Möglichkeit, WordPress über die Administrations-Oberfläche updaten zu können, und man verhindert auch die automatischen Sicherheitsupdates. Somit ist man als Admin in einem Dilemma. Mit dem FPM (FastCGI-Process-Manager) kann man beides haben: Sowohl Schreibrechte und automatische Updates, als auch Sicherheit. Das funktioniert, indem man für verschiedene Anwendungsfälle verschiedene PHP-User nutzt. Dazu konfiguriert man sich zwei Pools:

[admin]
user = www-user-admin
group = www-user-admin
listen = /var/run/php5-fpm-admin.sock
...

[wptest]
user = www-user-wptest
group = www-user-wptest
listen = /var/run/php5-fpm-wptest.sock
...

Anschließend übereignet man das Applikationsverzeichnis dem Admin-Nutzer. Die Gruppe bleibt beim Applikations-Nutzer. Die Rechte werden so verteilt, dass nur der User und nicht die Gruppe schreiben darf, außer im Upload-Verzeichnis:

chown -R www-user-admin:www-user-wptest /var/www/html
chmod -R u+w,g-w /var/www/html
chmod g+ws /var/www/html/wp-content/uploads

Zu guter Letzt muss man dem Webserver sagen, wann er welchen PHP-Socket verwenden soll. Im Apache 2.4 geht das mittlerweile extrem einfach:

<VirtualHost ...>
...
<FilesMatch "\.php$">
    <If "%{REMOTE_ADDR} in { '127.0.0.1', '1.2.3.4' }">
        SetHandler "proxy:unix:/var/run/php5-fpm-admin.sock|fcgi://localhost"
    </If>
    <Else>
        SetHandler "proxy:unix:/var/run/php5-fpm-wptest.sock|fcgi://localhost"
    </Else>
</FilesMatch>
<VirtualHost>

Wie man sieht wird der Handler abhängig von der Remote-Adresse gesetzt. Kommt der Request von lokal oder einer weiteren berechtigten IP, kommt der Admin-Nutzer zum Einsatz, andernfalls der „normale“ Applikations-Nutzer. Man kann natürlich auch andere Kritierien nutzen, anhand deren man entscheidet, welcher Socket zu nutzen ist. Eine Übersicht gibt es hier. So kann man beispielsweise auch eingeloggten Nutzern den administrativen Nutzer zuweisen, indem man auf das entsprechende Cookie prüft:

<If "%{HTTP_COOKIE} =~ /wordpress_logged_in_/">
...

Wichtiger Hinweis: Was nun nicht mehr funktioniert, ist ein automatisches Update über den integrierten WordPress-Cron-Mechanismus, der ausgelöst wird, wenn ein User die Seite aufruft. Daher sollte man einen manuellen Cronjob einrichten, der die wp-cron aufruft. Wie das geht erfahrt ihr hier.

Sichere PHP-Konfiguration

PHP ist eine weit verbreitete Sprache. Dennoch sind sich viele Leute nicht bewusst, dass man schon mit einigen wenigen PHP-Einstellungen die Sicherheit erheblich erhöhen kann:

  • open_basedir: Mit dieser Option kann man PHP verbieten auf Dateien außerhalb der spezifizierten Ordner zuzugreifen. Das ist extrem wichtig, um beispielsweise zu verhindern, dass ein PHP-Prozess /etc/passwd oder ähnliches auslesen kann. Wichtig ist zu wissen, dass intern nicht wirklich auf Verzeichnisse, sondern auf Präfixe gematcht wird, also wenn man auf /data/log

einschränkt, kann der User auch auf

/data/logs

zugreifen. Will man explizite Ordner angeben, muss der trailing slash mit angegeben werden. Außerdem muss man beachten, dass der temporäre Ordner von PHP innerhalb der open_basedirs liegt. Im Zweifelsfall sollte man für jede Instanz ein separates upload_tmp_dir und session.save_path setzen.

  • memory_limit: Das Memory-Limit bestimmt, wie viel RAM ein PHP-Skript verbrauchen darf. Welcher Wert hier sinnvoll ist, hängt von vielen Faktoren ab. Wieviel Speicher die eigene WordPress-Instanz benötigt, kann man mit Plugins anzeigen lassen (z.B.). Wie viel Puffer man reinräumt, hängt dann vom verfügbaren RAM auf dem Server ab. Beachtet auch, dass jeder Request einen Skriptaufruf triggert, und damit auch der RAM-Verbrauch im schlimmsten Fall auf das Produkt aus parallelen Requests und memory_limit steigen kann.
  • disable_functions: Mit dieser Direktive lassen sich PHP-Funktionen komplett deaktivieren. Das ist sinnvoll um zu verhindern, dass eine sogenannte PHP-Shell installiert wird. Ein Beispiel welches Funktionen deaktiviert, die potentiell gefährlich werden können, ist folgendes: disable_functions =exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

Eine weiter Funktion, die man abschalten kann ist phpinfo, da sie recht viel über ein System verrät.

Alle hier aufgezählen Maßnahmen verhindern nicht, dass man WordPress updaten und Plugins und Themes installieren kann.

Fazit

Wenn Ihr wichtige Maßnahmen kennt, die hier auftauchen sollten, lasst es mich bitte wissen.

Nächster Beitrag Vorheriger Beitrag