한 번 뜯어보는 github.dev 1-Click 토큰 탈취 익스플로잇
TLDR Dev에서 본 분석글. .ipynb 한 번 열기 = OAuth 토큰 통째 노출. WOW.
오늘도 메일로 온 TLDR Dev 보다가 분석글 하나를 발견했다.
How I almost stole your GitHub token with a 1-click VSCode exploit
제목부터 끌려서 한 번 정리해두고 싶어졌다.
요약하자면 — github.dev 에서 악성 .ipynb 한 번 클릭으로 내 모든 private repo에 접근 가능한 OAuth 토큰이 자동 탈취된다는 거다.
그게 다다. 클릭 한 번에 모든 게 끝나는 거다.
github.dev 가 어떻게 동작하길래?
먼저 배경부터. github.com 보다가 . 키 누르면 열리는 그 브라우저 안의 VSCode 있잖나. 그게 바로 github.dev다.
이 친구는 토큰을 이렇게 받는다.
github.com이 OAuth 토큰을 발급github.dev한테 POST로 넘김- github.dev 가 그걸 브라우저 localStorage 에 저장
- 이후 깃 작업할 때 그 토큰으로 GitHub API 호출
여기서 두 가지가 좀 거슬린다.
- 토큰이 full-repo scope다. 지금 보고 있는 레포만이 아니라 내가 접근 가능한 모든 레포에 대한 권한이라는 거다.
- localStorage 에 박혀 있다. JS로 접근 가능한 곳에 그냥 놓여 있다는 뜻이다.
즉, github.dev 안에서 임의 JS 한 번만 돌릴 수 있으면 그걸로 게임이 끝나버린다는 거다.
그럼 어떻게 임의 JS를 돌릴까? CSP도 빡세고 iframe 격리도 되어 있는데 말이다.
답은 Jupyter notebook이었다.
키보드 이벤트 위조
.ipynb 셀 안에 박힌 JS는 iframe 안에서 실행된다. 정상적으로는 부모 VSCode UI를 못 만진다.
근데 VSCode가 UX 편의성 때문에 keydown 이벤트는 webview → 메인 윈도우로 버블링되는 걸 허용해놨다. 노트북 안에서 키 입력해도 단축키가 먹혀야 하니까 그런 거다.
문제는 “진짜 키 입력”이랑 “JS가 위조한 키 입력”을 구분 못한다는 거다.
핵심 코드는 이 한 줄이다.
window.dispatchEvent(
new KeyboardEvent("keydown", {
key: "a", code: "KeyA", keyCode: 65,
ctrlKey: true, shiftKey: true
})
);
이걸로 Ctrl+Shift+A 같은 단축키를 JS로 위조해서 명령 팔레트를 열고, 또 위조 키로 “워크스페이스 익스텐션 설치” 명령을 호출하는 거다.
설치되는 익스텐션은 공격자가 미리 레포에 박아둔 거다. 익스텐션이 깔리고 나면 그냥 localStorage.getItem(...) 으로 토큰 들고 외부 서버에 fetch 한 번 쏘면 그걸로 상황 종료다.
공격 흐름
악성 .ipynb 가 있는 레포
↓ (사용자가 github.dev 에서 클릭)
notebook iframe 안에서 JS 실행
↓
KeyboardEvent 위조로 VSCode 명령 호출
↓
워크스페이스 익스텐션 자동 설치
↓
익스텐션이 localStorage 토큰 읽음
↓
공격자 서버로 전송
사용자가 한 행동은 클릭 1번이 전부다.
어디서 무너졌나
뜯어보면 결국 세 곳이 동시에 무너진 거다.
| 약점 | 내용 |
|---|---|
| 권한 범위 | github.dev 토큰이 full-repo scope. fine-grained 였으면 피해 한정 |
| 저장 위치 | 토큰이 JS-accessible 한 localStorage 에 있음 |
| 신뢰 경계 | iframe 안의 위조 키 입력이 메인 UI 까지 전파됨 |
한 군데만 제대로 막혀 있었어도 익스플로잇이 안 됐을 텐데, 셋이 동시에 살짝씩 허술했던 셈이다.
그래서 글쓴이가 “거의 털릴 뻔” 이라고 쓴 거다. CSP / iframe sandbox / DOMPurify 같은 다른 방어막이 살아있어서 끝까지 가진 않았다는 뉘앙스다.
defense-in-depth 라는 단어 책으로만 보다가 실전 사례로 보니까 와닿는다.
그래서 패치는?
이미 막혔다. Microsoft가 두 가지 조치를 했다.
- github.dev 에서 노트북 열 때 확인 다이얼로그 추가
- notebook webview 의 keydown 이벤트 버블링 차단
근데 같은 패턴의 변종은 또 나올 거다. 웹뷰에 임의 콘텐츠 띄우는 모든 곳이 다 잠재 공격면이니까 말이다.
메모
가벼운 글이라 길게 안 쓰고 메모만.
뭐, 어차피 github.dev가 뭔지 이번에 알았다.
- 모르는 레포의
.ipynb는.키로 열지 말기 - github.dev 안 쓸 땐 로그아웃 (localStorage 토큰 안 남게)
- Classic PAT 말고 fine-grained PAT 쓰기. Classic 은 사실상 admin
- VSCode “이 워크스페이스가 추천하는 익스텐션 설치할래?” 팝업, 무지성 yes 누르지 말기
뭐, 애초에 추천 익스텐션이 진짜 도움이 됐던 적이 없어서… 그냥 참고 정도만 해두면 될 거 같다.
마치며
한 줄 정리: 편의 기능 하나가 보안 경계 하나를 무너뜨릴 수 있다.
VSCode 가 keydown 버블링 허용한 건 진짜 사소한 UX 결정이었을 거다. 근데 그게 OAuth 토큰 탈취까지 연결됐다.
이 분석글이 좋았던 건 “이게 됨” 에서 멈추지 않고 왜 됐는지의 구조적 원인을 다 까놨다는 점이다.
익스플로잇 자체보다 이 분해 과정이 훨씬 더 재밌었다.
다음에 또 비슷한 분석글 보이면 정리해야겠다.
마침.
다른 글 보기
아 암튼 이해함, 작은 LLM으로도 제로데이를 발견한다는거..?
결론은 모델이 아닌 시스템이 중요하다~
마케팅은 진짜 너무 깊은 물이고, 나는 수영을 못한다
유저 100명 찍고 본격 그로스 한답시고 DM 50통, 메일 수십통 돌린 어제의 기록. 결과는... 아무도 안 옴.
Red Hat npm 패키지가 털렸다: Miasma
TLDR Sec 보다가 등골 서늘해진 사건. 한 명의 GitHub 계정에서 시작된 32개 패키지, 96개 버전, 11만 다운로드의 도미노.
한 번 뜯어보는 mattpocock/skills
Caveman이 개웃겨서 설치해봤는데, 의외로 매우 쓸만함