Untersuchung einer VPN-Appliance: Entdeckungen eines Forschers

Akamai Wave Blue

Verfasser

Ben Barnea

February 11, 2025

Akamai Wave Blue

Verfasser

Ben Barnea

Ben Barnea ist Sicherheitsforscher bei Akamai. Sein Fachgebiet ist die Durchführung von Sicherheits- und Schwachstellenforschung auf niedriger Ebene über verschiedene Architekturen hinweg, einschließlich Windows, Linux, Internet of Things und Mobilgeräten. Er lernt gerne, wie komplexe Mechanismen funktionieren und, was viel wichtigster ist, warum sie ausfallen.

Da VPNs ein Gateway in das Unternehmensnetzwerk sind, haben Schwachstellen in diesen Appliances große Auswirkungen.
Da VPNs ein Gateway in das Unternehmensnetzwerk sind, haben Schwachstellen in diesen Appliances große Auswirkungen.
  • Akamai-Forscher Ben Barnea hat mehrere Schwachstellen im FortiOS von Fortinet gefunden.
  • Ein nicht authentifizierter Angreifer kann Schwachstellen auslösen, die zu DoS und RCE führen können
  • Die DoS-Schwachstelle ist leicht auszunutzen und führt dazu, dass die Fortigate-Appliance nicht funktioniert.
  • Wir gehen davon aus, dass die RCE-Schwachstelle nur schwer auszunutzen ist.
  • Die Sicherheitslücken wurden verantwortungsbewusst an Fortinet weitergegeben und CVE-2024-46666 und CVE-2024-46668 zugewiesen.
  • Fortinet hat die von Barnea am 14. Januar 2025 entdeckten Schwachstellen behoben, und Geräte mit aktuellen FortiOS-Versionen sind davor geschützt.

Einführung

In den letzten Jahren waren VPN-Lösungen von vielen kritischen Schwachstellen betroffen, die von Cyberkriminellen im freien Internet ausgenutzt wurden. Einige dieser Schwachstellen lassen sich unglaublich einfach ausnutzen und haben verheerende Auswirkungen – etwa eine RCE auf einer VPN-Appliance, die über das Internet zugänglich ist. Sobald Angreifer ins Netzwerk gelangt sind, können sie sich lateral bewegen, um Zugriff auf sensible Daten, geistiges Eigentum und andere wertvolle Ressourcen zu erhalten.

Neben der ursprünglichen Ausnutzung der Schwachstellen in VPNs hat der Akamai-Forscher Ori David auch Post-Exploit-Techniken dokumentiert und gezeigt, dass ein kompromittierter VPN-Server Angreifern die Möglichkeit bietet, ganz einfach Kontrolle über andere kritische Ressourcen im Netzwerk zu erlangen.

Leider ist es für Sicherheitsforscher schwierig, mit der Untersuchung von VPN-Appliances zu beginnen. Das liegt daran, dass Firmware nicht immer leicht verfügbar und durch von Anbietern eingesetzte Verschlüsselungsmechanismen geschützt ist. Angesichts der Tatsache, dass VPN-Appliances Hauptziele für Ausbeutungsversuche sind, kann sich die Überwindung dieser Schutzmechanismen für Angreifer offenbar durchaus lohnen.

In diesem Blogbeitrag beschreiben wir, wie die Untersuchung der VPN-Lösung von Fortinet als Forschungsprozess verlief. Dabei gehen wir auf die Beschaffung der Firmware, die Entschlüsselungen, die Einrichtung eines Debuggers und schließlich die Suche nach Schwachstellen ein.

Einige der in diesem Beitrag vorgestellten Forschungen sind nicht neu. Es hat bereits einige ausgezeichnete Forschungsarbeiten zu FortiOS gegeben, unter anderem von Optistream, Bishop Fox, Assetnote und Lexfo.  Wir haben diese ersten Forschungen mit der neuesten Version von FortiOS aktualisiert, da Fortinet häufig die Ver- und Entschlüsselungsmethoden ändert, was die Analyse des Geräts erschwert.

Beschaffung eines Firmware-Images

Traditionell wurden VPNs als separate physische Appliance verkauft, was ihren Erwerb und das Extrahieren der Firmware erschweren konnte. Heutzutage gibt es VPN-Appliances jedoch viel häufiger als virtuelle Appliances, die auf einer virtuellen Maschine (VM) bereitgestellt werden können.

Glücklicherweise bietet Fortinet eine Test-VM an, die nach der Registrierung über die Website des Unternehmens heruntergeladen werden kann (Abbildung 1). Die VM ist eingeschränkt. Es ist nur eine CPU zulässig und sie ist auf 2 GB RAM begrenzt.

Fortinet bietet eine Test-VM an, die nach einer Registrierung von seiner Website heruntergeladen werden kann (Abbildung 1). Abb. 1: Herunterladbare VM-Tests

Erstellung einer Debugging-Umgebung

Die bereitgestellte VM bietet zwei besonders interessante Aspekte: (1) ein Boot-Image neben einem Kernel-Image (als flatkc bezeichnet) und (2) ein verschlüsseltes Dateisystem rootfs, das die meisten interessanten Dateien enthält. In dem Dateisystem finden wir nach der Entschlüsselung im /bin/-Verzeichnis eine Binärdatei namens init.

Die meisten Binärdateien der VM wurden statisch in diese eine Binärdatei kompiliert. Zwei interessante in /bin/init vorhandene Binärdateien sind der SSLVPND und der Management-Webserver. Wir kehren später in diesem Beitrag zu diesen Binärdateien zurück.

Wir wollen eine Umgebung schaffen, die uns eine vollständige Shell bietet und nicht die eingeschränkte CLI, die wir von Fortinet erhalten. Darüber hinaus wollten wir eine gdb-Binärdatei haben, mit der wir Binärdateien problemlos debuggen können.

Um eine solche Umgebung zu erstellen, geht man wie folgt vor:

  1. Das GZIP-komprimierte CPIO (ein Dateiarchivformat) entpacken
  2. Skript von Bishop Fox verwenden, um das rootfs zu entschlüsseln
  3. Das Archiv bin.tar.xz entpacken
  4. Patch von /bin/init zur Integritätsprüfung durchführen
  5. flatkc mit vmlinux-to-elf in ELF konvertieren
  6. Adresse von fgt_verify_initrd in IDA suchen, damit wir es in der Laufzeit patchen können, um weitere Integritätsprüfungen zu deaktivieren
  7. Eine statisch kompilierte Busybox und gdb in /bin/ ablegen
  8. Einen Stub kompilieren, der einen Telnet-Server erstellt; /bin/smartctl mit diesem Stub überschreiben
  9. Ordner /bin/ in tar-Paket verpacken
  10. Das rootfs erneut verpacken und dann verschlüsseln
  11. Am Ende des verschlüsselten rootfs Padding hinzufügen
  12. Das rootfs in der VMDK mit einer Helper-Ubuntu-VM ersetzen (dadurch wird die VMDK eingebunden)

Mit den in Abbildung 2 dargestellten Schritten wird eine bearbeitete VM mit Debugging-Funktionen erstellt. Da die Integritätsprüfung des rootfs nicht erfolgreich verläuft, stoppt der Kernel die Ausführung. Daher müssen wir entweder den Kernel (flatkc) patchen und dann auch den Integritätsprüfungscode des Bootloaders patchen, oder wir müssen alternativ die Integritätsprüfung des Kernel dynamisch patchen. Wir haben uns für den letztgenannten Ansatz entschieden.

Mit den in Abbildung 2 dargestellten Schritten wird eine bearbeitete VM mit Debugging-Funktionen erstellt. Abb. 2: Patching von FortiGate für eine Forschungsumgebung

Wir haben versucht, die VM-Debugging-Funktion von VMware zu verwenden, indem wir die VMDK-Datei bearbeitet haben. Dies sollte einen GDB-Debugger einrichten, der verfügbar ist, sobald die Maschine ausgeführt wird. Leider ist bei der Ausführung auf einer Maschine mit aktiviertem Hyper-V ein Problem mit dieser Funktionsimplementierung aufgetreten. Sobald ein Haltepunkt festgestellt wird, stürzt die Maschine ab. Dies scheint auf eine unvollständige Implementierung der Kernel-Debugging-Funktion zurückzuführen zu sein, wenn sie unter einer Hyper-V-Maschine ausgeführt wird.

Versuch, eine laufende VM mit QEMU zu erstellen

Nach mehreren fehlgeschlagenen Versuchen, Kernel-Debugging mit VMware auszuführen, entschieden wir uns, eine laufende VM mit QEMU zu erstellen. Wir haben die geänderte VM mit ähnlichen Schritten statisch erstellt, außer dass wir zwischen den Dateiformaten qcow2 und VMDK hin- und herkonvertieren mussten.

Um den Kernel bei der Verwendung von QEMU zu debuggen, können wir das -s-Flag an qemu-system bereitstellen. Schließlich führen wir die VM aus, hängen GDB an und fügen einen Haltepunkt hinzu, um die Integritätsprüfung zu überschreiben. Wir sehen dann die CLI. (Abbildung 3).

Schließlich führen wir die VM aus, hängen GDB an und fügen einen Haltepunkt hinzu, um die Integritätsprüfung zu überschreiben. Wir sehen dann die CLI. (Abbildung 3). Abb. 3: Eine funktionierende VM mit CLI

Nach der Konfiguration der Netzwerkeinstellungen und dem Empfang einer gültigen IP-Adresse vom DHCP-Server können wir die modifizierte smartctl-Binärdatei ausführen, die den aktuellen Verzeichnisinhalt und den Linux-ID-Befehl druckt und eine busybox-telnet-Sitzung öffnet. (Abbildung 4).

Nach der Konfiguration der Netzwerkeinstellungen und dem Empfang einer gültigen IP-Adresse vom DHCP-Server können wir die modifizierte smartctl-Binärdatei ausführen, die den aktuellen Verzeichnisinhalt und den Linux-ID-Befehl druckt und eine busybox-telnet-Sitzung öffnet. (Abbildung 4). Abb. 4: Aktivieren der Backdoor

Abbildung 5 zeigt schließlich, dass wir mit dem neu erstellten Telnet-Server verbunden wurden.

Abbildung 5 zeigt, dass wir mit dem neu erstellten Telnet-Server verbunden sind. Abb. 5: Verbindung zur Shell wird hergestellt

Sind wir schon fertig? Nein. 

Wie Sie in Abbildung 6 sehen können, verfügen wir nicht über eine gültige Lizenz, weshalb wir nicht mit dem Admin-Panel interagieren können.

Wie Sie in Abbildung 6 sehen können, verfügen wir nicht über eine gültige Lizenz, weshalb wir nicht mit dem Admin-Panel interagieren können. Abb. 6: Lizenzfehler

Du sollst nicht (?) umgehen

Zunächst dachten wir, die Lizenz sei ungültig, weil wir die Beschränkung auf 1 CPU und 2.048 MB RAM überschritten. Wir hatten also zwei Möglichkeiten: Wir konnten entweder diesen Grenzwert akzeptieren und eine sehr langsame VM haben oder ihn umgehen.

Nach einigen Umkehrungen fanden wir die Funktion upd_vm_check_license, die regelmäßig in einem Daemon aufgerufen wird (Abbildung 7). Die Funktion prüft, ob der RAM der Maschine und die Anzahl der CPUs des Rechners die Grenzwerte überschreiten.

Nach einigen Umkehrungen fanden wir die Funktion upd_vm_check_license, die regelmäßig in einem Daemon aufgerufen wird (Abbildung 7). Abb. 7: Dekompilierter Code, der für Beschränkungen der VM verantwortlich ist

Nachdem wir die Einschränkungen umgangen haben, indem wir den Rückgabewert von num_max_CPUs() und max_allowed_RAM() dynamisch geändert haben, haben wir jetzt eine leistungsfähige VM ohne Beschränkungen und erhalten weniger Fehler, wenn wir die Maschine starten. Wir erhalten jedoch immer noch eine Fehlermeldung bezüglich einer ungültigen Lizenz.

Nachdem wir viel zu viel Zeit damit verbracht hatten, die Lizenz-Validierungsfunktion umzukehren und über die Entscheidungen nachzudenken, die uns dazu geführt haben, stellten wir schließlich fest, dass die Lizenz den seriellen Schlüssel der Maschine verwendet, der mithilfe von SMBIOS UUID erstellt wird. Da wir QEMU nicht mit einer solchen bereitgestellt haben, verwendete er eine NULL-ID. Daher wurde die Seriennummer „FGVMEV00000000“ erstellt. Wir stellten eine SMBIOS-UUID an QEMU mit dem folgenden Flag bereit:

  -smbios type=1,manufacturer=t1manufacturer,product=t1product,version=t1version,serial=t1serial,
  uuid=25359cc8-5fe7-4d50-ab82-9fd15ecaf221,sku=t1sku,family=t1family

Dann starteten wir die Maschine und erhielten eine gültige Lizenz. 

Neue Version. Neue Verschlüsselung. WARUM?

An diesem Punkt verfügen wir über eine funktionierende Debugging-Umgebung. Wir begannen mit der Erforschung des Management-Webservers und fanden einige Schwachstellen, die wir später in diesem Beitrag beschreiben werden. Während der Suche nach diesen Schwachstellen bemerkten wir, dass Fortinet FortiOS Version 7.4.4 veröffentlicht hatte.

Wir wollten sehen, ob die Schwachstellen in der neuen Version noch vorhanden sind. Doch als es uns nicht gelang, das rootfs der aktualisierten VM zu entschlüsseln, fanden wir eine völlig andere Verschlüsselung vor, die noch schwieriger zu knacken ist. Dieses Mal wollten wir versuchen, das neue rootfs zu entschlüsseln. Dies sollte aber nicht auf die Erstellung einer Debugging-Umgebung hinauslaufen, da das Hauptziel zu diesem Zeitpunkt darin bestand, zu überprüfen, ob die Schwachstellen noch vorhanden sind.

Wir wollen zunächst die alte Methode (vor Version 7.4.4) zur Entschlüsselung des rootfs beschreiben:

1. Der Kernel überprüft die rootfs-Integrität, und wenn sie gültig ist, fährt er mit dem nächsten Schritt fort

2. Der Kernel ruft fgt_verifier_key_iv auf, wodurch ein Schlüssel und IV wie folgt berechnet werden:

a. Schlüssel: sha256() einer globalen Datenmenge

b. IV: sha256() eines anderen Teils derselben globalen Daten; schneidet dann das Ergebnis auf 16 Byte ab

3. Entschlüsselt das rootfs mithilfe von ChaCha20 mit dem Schlüssel und IV oben

Sehen wir uns nun den neuen Algorithmus an (Abbildung 8):

1. Entschlüsselungscode berechnet den Schlüssel und IV durch sha256() eines globalen Datenpuffers, wie im vorherigen Algorithmus

2. ChaCha entschlüsselt einen Speicherblock mit dem Schlüssel und IV; dieser Speicherblock ist ein ASN1, der einen privaten RSA-Schlüssel darstellt

3. Nimmt d,n aus dem privaten RSA-Schlüssel und entschlüsselt einen Datenblock, der in den letzten 256 Byte der verschlüsselten Firmware vorhanden ist, mit der bekannten Formel M = CD mod N

4. Nimmt aus dem Datenblock:

  • 16 Byte, die Nonce+Zähler sind 

  • 32 Schlüssel-Byte  

5. Führt die AES-Entschlüsselung im CTR-Modus mit dem Schlüssel und Nonce+Zähler durch; der Code verwendet eine nutzerdefinierte CTR-Ergänzung

6. Die Hinzufügung ergibt sich aus dem XORing der Nibbles des (Nonce+Zähler)

 

Neuer Algorithmus (Abbildung 8): Abb. 8: Flussdiagramm zur Firmware-Entschlüsselung

Um die Komplexität dieses mehrstufigen Entschlüsselungsalgorithmus noch zu erhöhen, hat das neue flatkc keine Symbole mehr, was das Schreiben von Tools zur automatischen Entschlüsselung der Firmware erschwert – zum Beispiel das Auffinden der globalen Daten, die für die ChaCha-Entschlüsselung und den verschlüsselten privaten RSA-Schlüssel verwendet werden.

Nachdem wir alle oben beschriebenen Schritte ausgeführt haben, können wir das rootfs anzeigen (Abbildung 9).

Nachdem wir alle oben beschriebenen Schritte ausgeführt haben, können wir das rootfs anzeigen (Abbildung 9). Abb. 9: Die tar-Datei des rootfs

Dieses Mal haben wir keine geänderte Umgebung erzeugt. Dies würde die Erstellung eines rootfs-Archivs erfordern, das wie oben beschrieben erfolgreich entschlüsselt werden muss. Eine weitere Option besteht darin, Haltepunkte dynamisch festzulegen und den Speicher mit einem entschlüsselten rootfs zu überschreiben.

Umkehrung des Admin-Webservers

Endlich können wir die Umkehrung des Admin-Webservers angehen. Er basiert auf Apache und sollte im Allgemeinen nicht für das Internet zugänglich sein (im Gegensatz zur sslvpn-Schnittstelle, auf die über das Internet zugegriffen werden kann).

Wenn wir die httpd-Konfigurationsdatei öffnen, sehen wir einige Speicherorthinweise, die URLs auf ihre Handler verweisen (Abbildung 10).

Wenn wir die httpd-Konfigurationsdatei öffnen, sehen wir einige Speicherorthinweise, die URLs auf ihre Handler verweisen (Abbildung 10). Abb. 10: Ausschnitt der httpd-Konfigurationsdatei

Anschließend können wir eine der Handler-Zeichenfolgen in der Binärdatei suchen, um die Handler-Tabelle zu finden (Abbildung 11).

Anschließend können wir eine der Handler-Zeichenfolgen in der Binärdatei suchen, um die Handler-Tabelle zu finden (Abbildung 11). Abb. 11: Liste der Handler, wie in IDA dargestellt

Da wir daran interessiert waren, nicht authentifizierte Schwachstellen zu finden, haben wir uns auf den api_authentication-handler konzentriert, der über die /api/v2/authentication-URL zugänglich ist.

Es empfiehlt sich, vor dem Einstieg in die Umkehrarbeit einige Apache-Strukturen und eine Verbindungsstruktur zu IDA zu erstellen, um die Arbeit zu erleichtern (Abbildungen 12 und 13).

  struct __attribute__((aligned(8))) _request_rec
{
    apr_pool_t *pool;
    conn_rec *connection;
    void *server;
    _request_rec *next;
    _request_rec *prev;
    _request_rec *main;
    char *the_request;
    int assbackwards;
    int proxyreq;
    int header_only;
    int proto_num;
    char *protocol;
    const char *hostname;
    unsigned __int64 request_time;
    const char *status_line;
    int status;
    enum http_methods method_number;
    const char *method;
    unsigned __int64 allowed;
    void *allowed_xmethods;
    void *allowed_methods;
    unsigned __int64 sent_bodyct;
    unsigned __int64 bytes_sent;
    unsigned __int64 mtime;
    const char *range;
    unsigned __int64 clength;
    int chunked;
    int read_body;
    int read_chunked;
    unsigned int expecting_100;
    void *kept_body;
    void *body_table;
    unsigned __int64 remaining;
    unsigned __int64 read_length;
    void *headers_in;
    void *headers_out;
    void *err_headers_out;
    void *subprocess_env;
    void *notes;
    const char *content_type;
    const char *handler;
    const char *content_encoding;
    void *content_languages;
    char *vlist_validator;
    char *user;
    char *ap_auth_type;
    char *unparsed_uri;
    char *uri;
    char *filename;
    char *canonical_filename;
    char *path_info;
    char *args;
    int used_path_info;
    int eos_sent;
    void *per_dir_config;
    void *request_config;
    void *log;
    const char *log_id;
    void *htaccess;
    void *output_filters;
    void *input_filters;
    void *proto_output_filters;
    void *proto_input_filters;
    int no_cache;
    int no_local_copy;
    void *invoke_mtx;
    apr_uri_t parsed_uri;
    apr_finfo_t finfo;
    void *useragent_addr;
    char *useragent_ip;
    void *trailers_in;
    void *trailers_out;
    char *useragent_host;
    int double_reverse;
    unsigned __int64 bnotes;
};

Abb. 12: Apache-Strukturen

  struct __attribute__((aligned(8))) conn_rec
{
    apr_pool_t *pool;
    void *base_server;
    void *vhost_lookup_data;
    apr_sockaddr_t *local_addr;
    sockaddr *client_addr;
    char *client_ip;
    char *remote_host;
    char *remote_logname;
    char *local_ip;
    char *local_host;
    __int64 id;
    void *conn_config;
    void *notes;
    void *input_filters;
    void *output_filters;
    void *sbh;
    void *bucket_alloc;
    void *cs;
    int data_in_input_filters;
    int data_in_output_filters;
    unsigned __int32 clogging_input_filters : 1;
    __int32 double_reverse : 2;
    unsigned int aborted;
    ap_conn_keepalive_e keepalive;
    int keepalives;
    void *log;
    const char *log_id;
    conn_rec *master;
    int outgoing;
};

Abb. 13: Eine Verbindungsstruktur

Beim Umkehren des Authentifizierungs-Handlers haben wir zuerst den POST-Methode-Handler mit dem Namen api_login_handler umgekehrt. Die Funktion ruft die Anmeldeparameter aus der Anfrage ab, indem sie api_login_parse_param abruft. Diese Funktion versucht, die POST-Daten abhängig vom Content-Type-Header zu parsen:

  1. Wenn „multipart/form-data“ eingestellt ist, hat die Anfrage ein HTML-Formular

  2. Wenn nicht, werden die einfachen POST-Daten gelesen

Die zweite Option ist relativ einfach, daher haben wir uns hauptsächlich auf den ersten Teil konzentriert. Beim Blick auf den dekompilierten Code fiel uns schnell eine Debug-Zeichenfolge auf, die uns auf die libapreq-Bibliothek verwies (Abbildung 14).

Beim Blick auf den dekompilierten Code fiel uns schnell eine Debug-Zeichenfolge auf, die uns auf die libapreq-Bibliothek verwies (Abbildung 14). Abb. 14: Eine Zeichenfolge, die angibt, dass der Code libapreq verwendet

Da es sich bei libapreq um eine Open-Source-Apache-Bibliothek handelt, hatten wir (fast) keinen Grund, nach Schwachstellen im dekompilierten Code anstatt im Quellcode zu suchen. Daher mussten wir als Erstes die Bibliotheksversion finden. Nach einigem Hin und Her konnten wir die Version eingrenzen, indem wir eine Zeichenfolge fanden, die in der Binärdatei und einem bestimmten Commit vorhanden ist, die aber einen Commit später entfernt wird (Abbildung 15).

Als Erstes mussten wir die Bibliotheksversion finden. Nach einigem Hin und Her konnten wir die Version eingrenzen, indem wir eine Zeichenfolge fanden, die in der Binärdatei und einem bestimmten Commit vorhanden ist, die aber einen Commit später entfernt wird (Abbildung 15). Abb. 15: Ein Vergleich zwischen dem dekompilierten binären Code und dem Quellcode-Commit, der die Zeichenfolge entfernt

Das Überraschende ist, dass die in der Binärdatei vorhandene Bibliothek die älteste verfügbare Version vom März 2000 ist (Abbildung 16).

Das Überraschende ist, dass die in der Binärdatei vorhandene Bibliothek die älteste verfügbare Version vom März 2000 ist (Abbildung 16). Abb. 16: Die eingegrenzte Version von libapreq

Schwachstellen

Fortinet verwendet das Modul fast genau so, wie es vor 25 Jahren war, abgesehen von sehr kleinen, zur Optimierung vorgenommenen Änderungen. Als wir das zum ersten Mal sahen, hielten wir es für ausgeschlossen, dass der Code von 2000 keine Schwachstellen aufweist. Und wir hatten recht!

Bevor wir uns die Schwachstellen ansehen, wollen wir den Zweck und die Verwendung der Bibliothek erklären. Apreq ist eine Apache-Bibliothek, die zur Verarbeitung von Client-Anfragedaten verwendet wird. Eine gängige Möglichkeit, Daten von einem Nutzer zu empfangen, sind HTML-Formulare. Die ausgefüllten Formulardaten können mit verschiedenen Codierungsmethoden an den Server übergeben werden, aber die üblichen Methoden sind application/x-www-form-urlencoded und multipart/form-data.

Wenn multipart/form-data verwendet wird, wählt der Client (in der Regel der Browser) einen beliebigen Text als Grenze zwischen verschiedenen Feldern der Formulardaten aus. Die Grenze wird über einen HTTP-Header angegeben. Die Grenze wird auch verwendet, um das Ende der Formulardaten anzugeben (Abbildung 17).

  POST /foo HTTP/1.1
  Content-Length: 68137
  Content-Type: multipart/form-data; boundary=ExampleBoundaryString

  --ExampleBoundaryString
  Content-Disposition: form-data; name="description"

  Description input value
  --ExampleBoundaryString
  Content-Disposition: form-data; name="myFile"; filename="foo.txt"
  Content-Type: text/plain

  [content of the file foo.txt chosen by the user]
  --ExampleBoundaryString--

Abbildung 17: Beispiel eines HTTP-Grenzformulars (Quelle)

Werfen wir nun einen Blick auf einige der Schwachstellen, die wir gefunden haben, einschließlich OOB-Schreibvorgang (Out-of-Bounds) von NULL-Byte, Wild Copy, Geräte-DoS, Webserver-DoS und OOB-Lesevorgang.

OOB-Schreibvorgang von NULL-Byte

Nachdem multipart_buffer_read den internen Puffer gefüllt und nach der Grenze gesucht hat, wird die Zeichenfolge zwischen der aktuellen Position und der gefundenen Grenze zurückgegeben. Der Fehler besteht in Folgendem: Wenn die Grenze nicht am Anfang des internen Puffers liegt, wird die Zeichenfolge zurückgegeben, nachdem die letzten beiden Zeichen entfernt wurden, die als Zeilenende („\r\n“) gelten sollen. Der Code geht fälschlicherweise davon aus, dass die Länge der zurückgegebenen Zeichenfolge größer als 2 ist.

In Abbildung 18 ist retval die zurückgegebene Zeichenfolge und start ihre Länge, was 1 entspricht. In diesem Fall ist blen auch gleich start. Es wird dann um zwei auf den Wert -1 verringert. Somit können wir NULL einen Byte vor dem Puffer schreiben.

In Abbildung 18 ist retval die zurückgegebene Zeichenfolge und start ihre Länge, was 1 entspricht. In diesem Fall ist blen auch gleich start. Es wird dann um zwei auf den Wert -1 verringert. Somit können wir NULL einen Byte vor dem Puffer schreiben. Abb. 18: Die Schwachstelle, die zu einem OOB-Schreibvorgang von NULL vor dem Puffer führt

Exploit-Versuche

Obwohl ein Byte Überlauf oder selbst ein Bit Überlauf ausreichen kann, um Codeausführung zu erreichen, ist es unserer Ansicht nach unwahrscheinlich, dass diese Schwachstelle in der Praxis ausgenutzt werden kann. Zunächst können wir nur ein NULL-Byte schreiben und nur ein Byte vor dem Puffer. Der Puffer wird auf dem Heap zugewiesen. Es gibt daher zwei Optionen:

1. Der Puffer ist der erste Puffer, der im Knoten des Heaps zugewiesen wird. In diesem Fall haben wir die Metadaten des Heap-Knotens vor unserer Zuweisung (Abbildung 19).

Der Puffer ist der erste Puffer, der im Knoten des Heaps zugewiesen wird. In diesem Fall haben wir die Metadaten des Heap-Knotens vor unserer Zuweisung (Abbildung 19). Abb. 19: Die Struktur, die einen Heap-Knoten eines Apache-Speichers darstellt

Wir überschreiben ein Byte des endp-Zeigers. Dies wirkt sich nicht auf den Wert des Zeigers aus, da wir das höchste Byte des Zeigers aufgrund der Byte-Reihenfolge überschreiben. Da die VM x64 ist, ist dieses Byte immer 0.

2. Wenn eine Zuordnung vor uns stattgefunden hat, können wir ein Datenbyte überschreiben. Leider haben wir, wie im vorherigen Beispiel, in den meisten Fällen entweder einen Zeiger am Ende der Struktur, Auffüllung oder eine C-Zeichenfolge, die bereits mit einem NULL-Abschlusszeichen beendet ist.

Wir haben ein interessantes Objekt gefunden: die Struktur multipart_buffer C (Abbildung 20).

Wir haben ein interessantes Objekt gefunden: die Struktur multipart_buffer C (Abbildung 20). Abb. 20: Die Struktur multipart_buffer C

In diesem Fall dachten wir, wir könnten buffer_len, die letzte Variable in der Struktur, über die vorherige Schwachstelle negativ ausfallen lassen und sie dann mit dieser Schwachstelle von Minus in einen großen positiven Wert ändern (indem wir das MSB-Byte überschreiben, das die Zahl als negativ markiert).

Obwohl dies als ein interessanter Ansatz erschien, gibt es zwei Probleme:

1. Die Struktur wird nur einmal erstellt – beim Erstellen des Formular-Parsers. Das bedeutet, dass wir dieses Objekt nicht leicht verteilen können.

2. Nachdem wir die vorherige Schwachstelle genutzt hatten, schränkten wir die Länge der Lesefunktion ein. Das bedeutet, dass nach dem Ende der Füllfunktion self->length nach dem Lesen der vollständigen POST-Daten der Anfrage 0 ist. Wenn der Code das nächste Mal multipart_buffer_read aufruft, würde es die Grenze im erweiterten Puffer nicht finden (da wir dafür gesorgt haben, dass der Endzeiger vor seinem Anfang angezeigt wird). Da außerdem self->length 0 ist, würde es mit einer Warnung vor einem fehlerhaften Upload beendet werden.

Wir erwogen, es in einem Versuch mit Race-Bedingung auszunutzen, aber wenn wir uns den Apache Multiprocessing Mode (MPM) ansehen, sehen wir Abbildung 21.

Wir erwogen, es in einem Versuch mit Race-Bedingung auszunutzen, aber wenn wir uns den Apache Multiprocessing Mode (MPM) ansehen, sehen wir Abbildung 21. Abb. 21: Apache MPM-Modus wird geprüft

Das bedeutet, dass Apache mehrere Prozesse generiert, die jeweils eine Anfrage verarbeiten. Wir können auch feststellen, dass diese Prozesse keine Multithread-Prozesse sind. Das bedeutet, dass eine Ausnutzung unter einer Race-Bedingung nicht möglich ist.

Wild Copy

In derselben Funktion, multipart_buffer_read, wird nur ein Teil des internen Puffers zurückgegeben, wenn der Code die Grenze nicht findet (start gleich -1): (Byte – boundary_length). Der Fehler besteht darin, dass Byte auf den konstanten Wert 5120 gesetzt ist, während die Begrenzungslänge viel größer sein kann (bis zur Grenze der Header-Länge).

Wenn wir also ein Feld senden, in dem sich die Grenze nicht im ersten Block befindet und die Begrenzungslänge größer als 5120 ist, können wir blen negativ machen. Dies führt dazu, dass der Code self->buffer vor den Puffer und self->buffer_len auf einen größeren Wert setzt (Abbildung 22).

Dies führt dazu, dass der Code self->buffer vor den Puffer und self->buffer_len auf einen größeren Wert setzt (Abbildung 22). Abb. 22: Die durch Integer-Unterlauf bedingte Schwachstelle

Exploit-Versuche

Es gibt einen Unterschied zwischen dieser Schwachstelle und der vorherigen. Da der Start negativ ist (die Grenze wurde nicht gefunden), gelangen wir dieses Mal nicht zu dem Code, der ein NULL-Byte schreibt.

blen ist ein Parameter für die Funktion multipart_buffer_read. Sehen wir uns also die Funktion multipart_buffer_read_body an, die sie aufruft und blen als Ausgabe empfängt.

Abbildung 23 zeigt, dass blen zweimal verwendet wird:

1. Wenn es der erste erzeugte Puffer ist, wird die von multipart_buffer_read empfangene Zeichenfolge mithilfe von blen dupliziert. In diesem Fall löst Apache einen OOM-Fehler aus (Out-of-Memory), und der Code wird abgebrochen. Dies kann zum Starten eines DoS-Angriffs verwendet werden.

2. Wenn es sich um den zweiten Block handelt, werden der vorherige und der aktuelle Block mithilfe der Funktion my_join verknüpft. Die Funktion ruft memcpy mit einer negativen Zahl auf, was zu einer Wild Copy führt.

Abbildung 23 zeigt, dass blen zweimal verwendet wird Abb. 23: Die beiden verschiedenen Flüsse in multipart_buffer_read_body

(Einige von Ihnen haben möglicherweise einen weiteren Bug in diesem Code bemerkt: old_len wird zu blen aktualisiert anstatt zu old_len + blen. Dies führt zu einem Abschneiden der Nutzerdaten.)

Die Ausnutzung einer solchen Wild Copy wäre sehr schwierig, sofern sie überhaupt möglich ist. Erstens haben wir keine Möglichkeit, die Wild Copy zu „stoppen“ – sie ist eine sehr große memcpy. Zweitens gibt es kein Multithreading. Daher können wir kein Objekt überschreiben, das gleichzeitig in einem anderen Thread verwendet wird. Wir gehen davon aus, dass die einzig mögliche Option darin besteht, den Signal-Handler auszunutzen, wenn er keine sichere Beendigung ausführt.

Geräte-DoS

Diese Schwachstelle liegt nicht in der Bibliothek selbst, sondern im Code von Fortinet, der die Bibliothek verwendet.

Wenn der Nutzer eine Datei über das Formular hochlädt, das mit einem Content-Disposition-Header innerhalb der POST-Daten angegeben ist (siehe Abbildung 17), wird im Ordner /tmp/ eine neue Datei mit dem Dateinamen /tmp/uploadXXXXXX erstellt, wobei die X zufällige Zeichen sind.

Für jeden Datei-Upload wird eine entsprechende Struktur erstellt und in eine verknüpfte Liste hochgeladener Dateien eingefügt. Der Fehler wird am Ende der Analyse angezeigt, wenn nur die Datei im ersten Knoten der verknüpften Liste gelöscht wird. Dadurch kann ein Angreifer einen Angriff initiieren, indem er den Ordner /tmp/ füllt. Da /tmp/ ein tmpfs-Dateisystem ist, werden die Daten im RAM gespeichert. Dies führt zu einem vollständigen System-OOM-Fall, der dazu führt, dass das Gerät sich aufhängt.

Nur ein Neustart des Geräts führt zur normalen Verwendung zurück – und selbst das ist nicht garantiert. Bei einem unserer Versuche haben wir eine Art Netzwerk-Brick auf das Gerät gebracht: Selbst nach dem Neustart des Geräts arbeiteten die Netzwerkfunktionen nicht ordnungsgemäß, und wir konnten das Gerät weder verwenden noch eine Verbindung zu ihm herstellen.

Exploit-Versuche

Diese Schwachstelle lässt sich ziemlich einfach ausnutzen. Man muss lediglich wiederholt Anfragen mit einem Formular senden, das mehrere Dateien enthält. Nach kurzer Zeit hängt das Gerät fest (Abbildung 24).

Man muss lediglich wiederholt Anfragen mit einem Formular senden, das mehrere Dateien enthält. Nach kurzer Zeit hängt das Gerät fest (Abbildung 24). Abb. 24: Das Ausfüllen des RAM der VPN-Appliance mit nicht gelöschten Dateien führt schließlich zu DoS, weil der Speicher nicht ausreicht

Webserver-DoS

Dies ist eine geringfügige Schwachstelle in der Funktion multipart_buffer_headers. Sie ruft multipart_buffer_fill auf, das den internen Puffer füllt, und sucht dann nach einer doppelten Zeile, die im internen Puffer endet.

Der Fehler besteht darin, dass der Code nach dem Aufruf von multipart_buffer_fill nicht prüft, ob der interne Puffer gültig ist. Wenn der Client die Verbindung abbrach, während multipart_buffer_fill auf Eingaben wartete, setzte er den Puffer auf Null, was zu einer NULL-Dereferenzierung führte (Abbildung 25).

Wenn der Client die Verbindung abbrach, während multipart_buffer_fill auf Eingaben wartete, setzte er den Puffer auf NULL, was zu einer NULL-Dereferenzierung führte (Abbildung 25). Abb. 25: Zugriff auf den internen Puffer, ohne zu prüfen, ob er gültig ist

Exploit-Versuche

Diese Schwachstelle lässt sich ebenfalls recht einfach ausnutzen. Ein Angreifer kann mehrere Threads erstellen, die eine Verbindung herstellen und die Absturzanfrage senden. Da Apache Pre-Fork-MPM keine ausnehmend gut Performance bietet, kann der Server die verschiedenen Abstürze nicht bewältigen und während des Angriffs keine anderen Clients bedienen.

OOB-Lesevorgang

Die Bibliothek verwendet einen internen Puffer, der regelmäßig gefüllt wird. Wenn er gefüllt ist, wird die Anzahl der zu lesenden Byte wie folgt berechnet:

1. Berechnen (bytes_requested - current_internal_buffer_len)

2. Begrenzungslänge + 2 hinzufügen (für „\r\n\r\n“)

3. Das Minimum zwischen diesem Ergebnis und den verfügbaren Byte berechnen, die aus den POST-Daten gelesen werden sollen

Die Schwachstelle befindet sich in multipart_buffer_read. Wenn die Grenze am Anfang des internen Puffers gefunden wurde und es nicht die Grenze ist, die das Ende des Formulars markiert, verschiebt es den internen Pufferzeiger über die Grenze hinaus und fügt weitere 2 Byte für das Zeilenende hinzu.

Hier liegt der folgende Fehler vor: Ein Angreifer kann bewirken, dass der interne Puffer genau die Größe der Grenze hat, wodurch der Code um zwei Byte über das Ende des internen Puffers hinausgeht (Abbildung 26). Dies ist möglich, da wir die Berechnung des internen Puffers „begrenzen“ können, indem wir eine geringere Byte-Anzahl angeben als die boundary_length + bytes_requested.

Hier liegt der folgende Fehler vor: Ein Angreifer kann bewirken, dass der interne Puffer genau die Größe der Grenze hat, wodurch der Code um zwei Byte über das Ende des internen Puffers hinausgeht (Abbildung 26). Abb. 26: Vorschieben des internen Puffers über sein Ende hinaus

Exploit-Versuche

Wie in Abbildung 26 gezeigt, gibt der Code NULL zurück. Wie wir jedoch im vorherigen Bug gesehen haben, greift der Code in multipart_buffer_headers direkt auf den internen Puffer zu und nicht auf den Rückgabewert. Daher sucht der Code nach „\r\n“ nach dem Puffer und gibt ihn als Header zurück.

Bei der Verwendung von apreq im Anmelde-Handler liest der Code die Formularfelder, sendet sie aber nicht an den Client zurück. Daher können wir die OOB-Schwachstelle in diesem Fall nicht als Informationsleck verwenden. Die Bibliothek wird ebenfalls mehrmals im Webserver verwendet, aber es scheint, als wäre sie nur authentifizierten Nutzern zugänglich. Daher könnte ein Angreifer mit Anmeldeinformationen für Nutzer mit niedrigen Berechtigungen sie allenfalls als Schwachstelle zur Erhöhung von Berechtigungen verwenden, indem er Anmeldeinformationen für Nutzer mit hohen Berechtigungen aus dem Speicher liest.

SSLVPND

SSLVPND ist der Daemon, der für die Verarbeitung der SSL-VPN-Komponente von Fortinet verantwortlich ist. Es ist über das Internet zugänglich. Die apreq-Bibliothek wird auch im SSLVPND verwendet. Die dort verwendete Bibliothek basiert auf derselben alten Version von apreq, mit einigen Änderungen. Alle oben beschriebenen Schwachstellen sind, mit Ausnahme der Geräte-DoS-Schwachstelle, auch im SSLVPND vorhanden.

Leider konnten wir die apreq-Bibliothek in SSLVPND nicht auslösen. Daher können wir nicht sagen, ob diese Schwachstellen für nicht authentifizierte Nutzer zugänglich sind oder ob sie unter Umständen im SSLVPND-Kontext ausgenutzt werden können.

Zusammenfassung

Angreifer nehmen oft VPNs ins Visier, da sie über das Internet zugänglich sind. In diesem Blogbeitrag haben wir uns ein Beispiel für einen Ansatz zur Erforschung einer VPN-Appliance angesehen. Zwar wurden keine kritischen Schwachstellen identifiziert, doch es ist unserer Ansicht nach davon auszugehen, dass noch weitere Schwachstellen darauf warten, entdeckt zu werden.

Da VPNs ein Gateway in das Unternehmensnetzwerk sind, haben Schwachstellen in diesen Appliances große Auswirkungen. Wir hoffen, dass dieser Blogbeitrag auch andere Sicherheitsexperten ermutigt, nach VPN-Schwachstellen zu suchen.



Akamai Wave Blue

Verfasser

Ben Barnea

February 11, 2025

Akamai Wave Blue

Verfasser

Ben Barnea

Ben Barnea ist Sicherheitsforscher bei Akamai. Sein Fachgebiet ist die Durchführung von Sicherheits- und Schwachstellenforschung auf niedriger Ebene über verschiedene Architekturen hinweg, einschließlich Windows, Linux, Internet of Things und Mobilgeräten. Er lernt gerne, wie komplexe Mechanismen funktionieren und, was viel wichtigster ist, warum sie ausfallen.