ChatGPT가 Cloudflare Turnstile로 React 앱 상태까지 검사한다는 걸 암호 해독으로 밝혀냈다
ChatGPT Won't Let You Type Until Cloudflare Reads Your React State
TL;DR Highlight
Cloudflare Turnstile은 암호화된 바이트코드 역공학 해독으로 ChatGPT 메시지 전송 전 브라우저 지문은 물론 React 앱 내부 상태(__reactRouterContext 등)까지 검사한다.
Who Should Read
봇 탐지/방지 시스템의 작동 원리가 궁금한 보안 개발자, 또는 자동화 도구나 스크래퍼를 만들면서 Cloudflare Turnstile의 내부 구조를 이해하고 싶은 개발자.
Core Mechanics
- ChatGPT는 메시지를 전송할 때마다 Cloudflare Turnstile 프로그램을 브라우저에서 조용히 실행한다. 이 프로그램은 요청마다 새로 암호화된 바이트코드(28,000자 base64)로 내려오며, 사용자는 이 검사가 끝나야 입력이 가능해진다.
- 암호화 방식은 두 겹이다. 외부 레이어는 prepare 요청의 p 토큰으로 XOR 복호화하고, 내부 19KB blob은 바이트코드 안에 float 리터럴(예: 97.35)로 박혀 있는 키로 XOR 복호화한다. 키가 페이로드 안에 포함되어 있어 HTTP 요청/응답만 있으면 완전히 복호화할 수 있다. 저자가 50개 요청에서 50번 모두 검증했다.
- 복호화된 프로그램은 커스텀 VM(28개 opcode)으로 작성되어 있으며, 레지스터 주소가 요청마다 랜덤화된다. 저자는 Cloudflare SDK 소스(sdk.js, 1,411줄)를 디컴파일해 opcode를 역추적했다.
- 프로그램이 수집하는 55개 속성은 세 레이어로 나뉜다. 1레이어는 브라우저 지문(WebGL 8개, 화면 8개, 하드웨어 5개, 폰트 측정 4개, DOM 8개, 스토리지 5개)이고, 2레이어는 Cloudflare 엣지 헤더(도시, 위도/경도, IP, 지역 등 5개)로 Cloudflare 네트워크를 통과한 요청에만 존재한다.
- 가장 주목할 만한 3레이어는 React 앱 상태 검사다. __reactRouterContext(React Router v6+가 DOM에 붙이는 내부 객체), loaderData(라우트 로더 결과), clientBootstrap(ChatGPT SSR 하이드레이션 전용 객체) 세 가지를 확인한다. 이 값들은 JavaScript 번들이 실제로 실행되고 React가 완전히 hydrate돼야만 존재한다.
- 이 때문에 브라우저 API만 흉내 내거나 HTML만 파싱하는 봇은 React 내부 상태를 재현할 수 없어 탐지된다. 이는 브라우저 레이어가 아닌 '애플리케이션 레이어'의 봇 탐지라고 저자는 설명한다.
- 수집된 지문은 localStorage에 키 '6f376b6560133c2c'로 저장되어 페이지 로드 간 지속된다. 최종적으로 55개 속성을 JSON으로 직렬화해 토큰을 생성하는 4개의 VM 명령으로 마무리된다.
- 377개의 Turnstile 프로그램 샘플을 분석했는데, 수집 속성 55개는 모든 샘플에서 변함없이 동일했다. 암호화 구조와 VM 레지스터 주소만 요청마다 달라진다.
Evidence
- OpenAI의 Integrity 팀 직원 Nick이 직접 댓글을 달았다. 이 검사는 봇, 스크래핑, 사기 등 플랫폼 남용을 막기 위한 것이며, 로그아웃 상태 무료 사용을 유지하기 위해 GPU 자원을 진짜 사용자에게 집중하려는 목적이라고 설명했다. 또한 페이지 로드 시간, 첫 토큰 시간, 페이로드 크기를 모니터링하며 대부분 사용자에게는 영향이 미미하고, 일부 소수만 약간의 지연을 겪는다고 밝혔다.
- 'React 앱이 완전히 hydrate돼야 속성이 존재한다'는 점이 봇 탐지의 핵심이라는 저자 주장에 대해, 'Headless 브라우저도 JavaScript를 실행하는데 이게 의미 있는 방어인가?'라는 반론이 있었다. 실제로 Chrome headless는 JS 번들을 실행하므로, 진짜 핵심은 '브라우저 API만 스텁으로 대체한 경량 봇 프레임워크'를 걸러내는 것에 가깝다는 의견도 나왔다.
- 완전한 Windows 11 VM에 Chrome과 GPU 가속을 켜서 실행하면 이 모든 검사를 우회할 수 있고, AWS 기준으로 페이지 로드 1,000회당 1센트 정도라 충분히 저렴하다는 의견이 있었다. 메모리 페이지 중복 제거 기술을 쓰면 동시에 50개 VM을 돌릴 수 있다는 구체적인 수치도 언급됐다.
- Cloudflare 관련 사용자 경험 불만도 제기됐다. Firefox 사용이나 '의심스럽다'고 판단된 IP 사용 시 캡챠를 과도하게 마주친다는 불만이 있었고, M5 MacBook Pro에서도 긴 대화 후 ChatGPT 탭이 멈추는 UX 문제를 겪는다는 사용자도 있었다.
- 이 글의 'So what?' 문제도 지적됐다. 결국 OpenAI가 자사 React 앱을 제대로 실행한 사용자만 받겠다는 건데, 그게 뭐가 문제냐는 반응이 여럿이었다. 역공학 자체는 흥미롭지만 '그래서 무료 API 추론이 가능해졌나요?'라는 댓글처럼, 실질적 결론이 불명확하다는 평이 많았다.
How to Apply
- 봇 탐지 시스템을 자체 서비스에 구축하려는 경우, React 앱 상태(__reactRouterContext, loaderData 같은 hydration 결과물)를 서버로 전송해 실제 SPA가 실행됐는지 검증하는 애플리케이션 레이어 검사를 추가하면, 브라우저 API만 흉내 낸 경량 봇을 효과적으로 걸러낼 수 있다.
- Cloudflare Turnstile을 도입하려는 경우, 이 분석을 통해 Turnstile이 단순 CAPTCHA가 아니라 55개 속성을 수집하는 VM 기반 지문 수집 시스템임을 이해하고, 사용자 지연 및 프라이버시 트레이드오프를 사전에 고려해 도입 여부를 결정할 수 있다.
- 자동화 테스트나 E2E 테스트 환경에서 ChatGPT나 Cloudflare Turnstile이 걸린 사이트를 테스트해야 한다면, API 스텁이나 경량 headless 모드 대신 실제 Chromium을 GPU 렌더링과 함께 실행해 React hydration까지 완료시켜야 Turnstile 검사를 통과할 수 있다.
- 보안 리서치 목적으로 Turnstile 바이트코드를 분석하려면, 저자가 공개한 5단계 복호화 체인(p 토큰 XOR → float 키 추출 → 내부 blob 복호화)을 참고해 HTTP 트래픽 캡처만으로 프로그램 전체를 복호화할 수 있다.
Code Example
# 저자가 공개한 Turnstile 외부 레이어 복호화 코드
import base64, json
# Step 1: prepare 요청에서 p_token, 응답에서 dx를 읽어온다
# Step 2: XOR 복호화로 외부 바이트코드(89개 VM 명령) 추출
outer = json.loads(bytes(
base64.b64decode(dx)[i] ^ p_token[i % len(p_token)]
for i in range(len(base64.b64decode(dx)))
))
# → 89개 VM 명령 배열
# Step 3: 내부 19KB blob 이후 5-arg 명령어를 찾아 마지막 인수(float)를 XOR 키로 사용
# 예: [41.02, 0.3, 22.58, 12.96, 97.35] → 키는 97.35
# Step 4: str(key)로 내부 blob XOR 복호화 → 417~580개 VM 명령 포함 JSON
inner = json.loads(bytes(
base64.b64decode(blob)[i] ^ ord(str(key)[i % len(str(key))])
for i in range(len(base64.b64decode(blob)))
))
# Turnstile이 확인하는 React 앱 상태 속성 (Layer 3)
# - window.__reactRouterContext (React Router v6+ DOM 내부 객체)
# - loaderData (라우트 loader 결과)
# - clientBootstrap (ChatGPT SSR hydration 전용)
# localStorage에 지문이 저장되는 키
FINGERPRINT_KEY = '6f376b6560133c2c'Terminology
관련 논문
Claude Design 구독 해지 후 프로젝트 접근 불가 경험담 및 주의사항
Claude Design 구독을 해지했더니 기존 프로젝트에 접근이 완전히 차단됐다는 사용자 경고로, AI 도구에 중요한 작업물을 의존할 때의 리스크를 잘 보여주는 사례다.
TanStack NPM 공급망 공격 사후 분석 (Postmortem)
2026년 5월 11일 TanStack의 42개 npm 패키지가 GitHub Actions cache poisoning과 OIDC 토큰 탈취를 조합한 공격으로 악성 버전이 배포됐으며, 공격 벡터와 대응 과정을 상세히 분석한 글이다.
LLM이 TLA+로 실제 시스템을 제대로 모델링할 수 있을까? — SysMoBench 벤치마크
LLM이 TLA+ 명세를 작성할 때 문법은 잘 통과하지만 실제 시스템과의 동작 일치도(conformance)는 46% 수준에 그친다는 걸 체계적으로 검증한 벤치마크 연구로, AI 기반 형식 검증의 현실적 한계를 보여준다.
Natural Language Autoencoders: Claude의 내부 활성화를 자연어 텍스트로 변환하는 기법
Anthropic이 LLM 내부의 숫자 벡터(활성화값)를 직접 읽을 수 있는 자연어로 변환하는 NLA 기법을 공개했다. AI가 실제로 무슨 생각을 하는지 해석하는 interpretability 연구의 새로운 진전이다.
ProgramBench: LLM이 프로그램을 처음부터 다시 만들 수 있을까?
LLM이 FFmpeg, SQLite, PHP 인터프리터 같은 실제 소프트웨어를 문서만 보고 처음부터 재구현할 수 있는지 측정하는 새 벤치마크로, 최고 모델도 전체 태스크의 3%만 95% 이상 통과하는 수준에 그쳤다.
첫 번째 토큰이 이미 알고 있다: Single-Decode Confidence로 Hallucination 탐지하기
LLM이 답변의 첫 토큰을 생성할 때의 확률 분포만 봐도, 10번 샘플링하는 semantic self-consistency와 맞먹는 hallucination 탐지 성능이 나온다.