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
관련 논문
2,000명이 내 AI 어시스턴트를 해킹하려 한 뒤 벌어진 일
실제로 6,000개 이상의 이메일로 AI 에이전트에 prompt injection 공격을 시도한 공개 실험 결과로, Claude Opus 4.6이 비밀 파일 유출을 한 번도 허용하지 않았지만 실험 설계의 현실성에 대한 논란이 뜨거웠다.
언제 LLM을 조합하면 효과가 있나? 67개 Frontier 모델에서 Routing, Voting, Mixture-of-Agents의 Co-Failure Ceiling 분석
여러 LLM을 조합해도 '모든 모델이 동시에 틀리는 비율(β)'이 성능 상한선이며, 업계가 쓰는 pairwise 상관계수(ρ)는 이 상한선을 예측하지 못한다.
Function Calling을 넘어서: Tool-Environment 신뢰성 문제 하에서의 Tool-Using Agent 벤치마크
실제 환경처럼 API가 망가지거나 결과가 이상할 때 LLM 에이전트가 얼마나 잘 버티는지 측정하는 벤치마크 ToolBench-X 공개.
LG 스마트 TV 앱의 절반 가까이에 Residential Proxy SDK가 심어져 있다
6,038개의 LG·Samsung 스마트 TV 앱을 스캔했더니 2,058개에서 사용자의 IP를 몰래 팔아 트래픽을 중계하는 Residential Proxy SDK가 발견됐다. TV는 컴퓨터처럼 감시받지 않아서 프록시 호스트로 거의 이상적인 환경이다.
Prompt Injection의 본질은 Role Confusion이다
LLM이 시스템 프롬프트, 사용자 입력, 툴 출력을 구분하지 못하는 구조적 결함이 prompt injection의 근본 원인이라는 ICML 2026 논문으로, 현재 LLM 보안 아키텍처의 한계를 명확히 분석한다.
GPT-5.5의 환각(Hallucination) 비율이 MIT 라이선스 GLM-5.2보다 3배 높다
모델 크기가 커질수록 성능이 좋아진다는 통념에 반해, 오픈소스 753B 모델 GLM-5.2가 추정 1~2T 규모의 GPT-5.5보다 환각 비율이 3배 낮다는 벤치마크 결과가 나왔다. 단순히 파라미터 수와 벤치마크 점수만으로 모델을 선택하면 실제 업무에서 낭패를 볼 수 있다는 경고다.