계산해 봐: 링크 하나로 ChatGPT 계정을 탈취한 방법
Do the math: How we took over ChatGPT accounts
TL;DR Highlight
공개 공유 링크 하나로 ChatGPT 피해자 세션에서 JS 실행, 비공개 대화 유출 가능했다
Who Should Read
iframe 샌드박스와 postMessage로 사용자 생성 HTML을 미리보기로 보여주는 기능을 만드는 프론트엔드·보안 담당 개발자
Core Mechanics
- Preview 샌드박스는 뚫리지 않았다
- HTML Preview는 다른 도메인의 제한된 프레임에서 돌아가 parent.document를 읽지 못했다.
- 문제는 메인 페이지의 다른 컴포넌트가 Preview의 메시지를 받아줬다는 점이다.
- MathLive가 보낸 사람을 확인 안 했다
- 그래프가 MathLive를 불러오면서 메인 페이지에 postMessage 리스너(메시지 수신기)가 설치됐다.
- origin 정책이 none이라 어느 주소에서 온 메시지든 받았고, 허용된 창인지도 확인하지 않았다.
- 키보드 레이아웃에 HTML이 그대로 들어갔다
- update-setting 메시지로 바꾼 레이아웃이 innerHTML에 들어갔다.
- createHTML은 정제 함수가 아니라 문자열을 신뢰 표시만 해서 그대로 통과시켰다.
- sandbox 없는 iframe이 chatgpt.com 권한을 얻었다
- MathLive가 sandbox 속성 없이 srcdoc iframe을 메인 문서에 넣었다.
- 메인 페이지가 만든 프레임이라 https://chatgpt.com 신원을 물려받아 부모 페이지에 접근할 수 있었다.
- CSP는 YouTube JSONP로 우회했다
- CSP는 인라인 JS를 막았지만 YouTube 스크립트는 허용했다.
- YouTube oEmbed가 잘못된 callback도 응답에 넣어 돌려줘서, callback 끝에 ;void를 붙여 실행시켰다.
- 공개 공유 링크 하나로 계정 탈취 수준이 됐다
- 그래프와 HTML Preview를 한 공개 대화에 넣어 /share/<id> 링크에 공격 체인이 보존됐다.
- 피해자가 인증된 API로 대화 목록과 비공개 대화 내용을 조회하게 됐고, 일부 데이터를 외부 서버로 전송하는 것까지 확인했다.
Community Reactions
- 공격에 링크 클릭이 필요하지만 보상금은 낮다는 의견
- 공격 체인이 심각한데 $5,000은 너무 적다는 반응이 다수였다.
- 보상이 낮으면 다른 연구자가 신고하지 않을 수 있다는 우려도 나왔다.
- 보상금을 낮게 두는 게 전략적일 수 있다는 의견
- 보상이 낮으면 관심 갖는 사람이 줄어든다는 게 큰 회사들의 계산이라는 의견이 있었다.
- 5천 달러든 5만 달러든 회사에는 큰 차이가 없다는 주장이었다.
- 글 설명이 불친절하다는 지적
- JSONP 설명이 우회 방법 설명으로 넘어가 흐름 파악이 어렵다는 의견이 있었다.
- 모델이 HTML 블록을 생성하게 만드는 부분을 어떻게 통제했는지 설명이 없다는 지적도 있었다.
- 저자 회사의 영업 방식에 대한 불만 경험
- 허락 없이 테스트 환경을 점검해 취약점을 알려 온 뒤 서비스를 홍보했다는 경험담이 있었다.
- 버그 바운티가 없는데 이런 접근은 환영받지 못하고 불법이라는 의견이었다.
How to Apply
- postMessage 리스너 전수 점검하기
- 오늘 프론트엔드 코드에서 addEventListener('message')를 검색하라.
- event.origin과 event.source를 둘 다 확인하는지 보고, 없으면 상태를 바꾸는 핸들러부터 고쳐라.
- 메시지로 받은 설정에서 raw HTML 제거하기
- 라이브러리가 postMessage나 설정값을 innerHTML에 넣는 경로를 찾아라.
- HTML 대신 텍스트나 구조화된 데이터만 받게 바꾸고, 필요하면 정제기를 거쳐라.
- 동적으로 만드는 iframe에 sandbox 붙이기
- 외부 입력이 영향을 줄 수 있는 srcdoc·동적 iframe을 찾아라.
- sandbox 속성을 기본으로 넣어 부모 origin을 상속하지 않게 하라.
- 공유·미리보기 기능을 기능 간 결합으로 테스트하기
- 공개 공유, 미리보기, 서드파티 라이브러리가 한 페이지에서 만나는 시나리오를 테스트 목록에 넣어라.
- CSP에서 허용한 외부 도메인에 JSONP 엔드포인트가 있는지 점검하라.
Try It
// Claude나 ChatGPT에 붙여넣기: 프론트엔드 코드 보안 리뷰 요청
아래는 우리 프론트엔드의 postMessage 관련 코드다.
{{코드}}
다음을 점검해 줘.
1. message 리스너가 event.origin과 event.source를 둘 다 검증하는가
2. 메시지 데이터가 innerHTML 등 HTML 싱크로 들어가는 경로가 있는가
3. 동적으로 만든 iframe(srcdoc 포함)에 sandbox 속성이 있는가
4. CSP에서 허용한 외부 도메인 중 JSONP callback을 지원하는 엔드포인트가 있는가
문제가 되는 줄과 수정 코드를 함께 보여줘.
이어서 해 볼 것: 수정 후 다른 origin의 iframe에서 가짜 메시지를 보내 거부되는지 테스트해 봐.Terminology
관련 논문
ChatGPT 무제한 채팅에 MCP를 붙여 코딩 에이전트 풀로 쓴 후기
ChatGPT 채팅에 로컬 MCP를 붙여 Codex 한도 없이 병렬 코딩을 돌렸다
GPT-5.6 Sol, 평화 모드 Factorio를 13.5시간에 클리어
GPT-5.6 Sol이 Codex 낮은 사고 수준으로 평화 모드 Factorio 로켓 발사까지 갔다
AI가 SRE를 대체하는 게 아니라 실력을 깎아먹는다
인시던트 95%를 AI에 맡기자 나머지 5%를 다룰 SRE의 실력이 녹슬 수 있다
Cursor Marketplace의 Claude Code 확장에 심어진 SSH 백도어
Cursor Marketplace의 변조된 Claude Code 확장이 SSH 백도어를 심었다
AI-native 엔지니어링 조직 만들기: FurtherAI 9개월 회고
배포 전 과정을 단계별로 자동화하자 엔지니어 1인당 일일 머지가 약 5배 늘었다
Minecraft·Mindustry에서 AI 에이전트 군집 50마리를 돌려 봤다
에이전트 50개를 Minecraft에 풀었더니 배급 장부와 관료제가 생겼다
Cursor Agent SDK 출시: 에이전트를 코드로 정의하고 한 줄로 배포
Cursor가 에이전트를 코드로 정의·배포하는 Agent SDK를 정식 공개했다
희귀해도, 관련 있어도 틀린다: LLM 그룹 의사결정에서 메모리가 실패하는 방식
에이전트 토론 요약을 '희귀 우선'이나 '관련성 우선'으로 고르면 결론이 쉽게 틀어진다