ChatGPT는 Cloudflare가 React 상태를 읽을 때까지 입력을 차단함
4 hours ago
1
- ChatGPT의 메시지 전송 시 Cloudflare Turnstile 프로그램이 실행되어 브라우저 지문뿐 아니라 React 애플리케이션 상태까지 검사함
- 복호화된 프로그램은 55개 속성을 수집하며, 브라우저·네트워크·애플리케이션 3계층으로 나뉜 검증 절차를 수행함
- React 렌더링이 완료된 실제 SPA 환경에서만 통과 가능해, 헤드리스 브라우저나 단순 봇 요청은 실패함
- 수집된 지문은 암호화되어 OpenAI-Sentinel-Turnstile-Token 으로 변환되고, 여기에 Signal Orchestrator와 Proof of Work 모듈이 추가로 동작함
- Cloudflare 서버만 복호화 키를 알고 있어, 프라이버시 경계가 기술이 아닌 정책으로 결정되는 구조임
ChatGPT 메시지 전송 시 Cloudflare Turnstile 동작 구조 분석
-
ChatGPT의 모든 메시지 전송 시 브라우저 내에서 Cloudflare Turnstile 프로그램이 자동 실행됨
- 네트워크 트래픽에서 377개의 Turnstile 프로그램을 복호화한 결과, 일반적인 브라우저 지문 수집을 넘어 React 애플리케이션 상태까지 검사함
- 단순한 브라우저 지문 위조 봇은 통과하지 못하고, ChatGPT의 SPA(단일 페이지 애플리케이션) 을 완전히 렌더링해야 검증 통과 가능
암호화 구조와 복호화 과정
- Turnstile 바이트코드는 서버 응답의 turnstile.dx 필드로 전달되며, 매 요청마다 28,000자 길이의 base64 문자열로 암호화되어 있음
- 외부 암호화층은 p 토큰과 XOR 연산으로 복호화 가능하며, 두 값은 동일한 HTTP 요청 내에서 교환됨
- 복호화된 결과는 89개의 VM 명령어로 구성된 JSON 형태의 바이트코드
- 내부에는 19KB 크기의 추가 암호화 블롭(blob) 이 존재하며, 이 블롭은 다른 XOR 키로 암호화되어 있음
- 키는 바이트코드 내부의 float 리터럴 값(예: 97.35) 로 포함되어 있으며, 서버가 생성하여 브라우저로 전송
- 50회 요청 모두에서 동일한 방식으로 유효한 JSON 복호화 확인
- 전체 복호화 절차는 다음 5단계로 구성
-
p 토큰을 요청에서 읽음
- 응답의 turnstile.dx를 읽음
-
XOR(base64decode(dx), p) → 외부 바이트코드 생성
- 19KB 블롭 뒤의 5인자 명령어에서 마지막 인자를 키로 추출
-
XOR(base64decode(blob), str(key)) → 내부 프로그램 복호화 (417~580개 명령어)
복호화된 프로그램의 검사 항목
- 내부 프로그램은 28개의 명령어(opcode) 를 가진 커스텀 VM으로 실행되며, 요청마다 부동소수 레지스터 주소가 무작위로 변경됨
- 총 55개 속성(property) 을 수집하며, 377개 샘플 모두 동일한 항목을 포함
-
Layer 1: 브라우저 지문
-
WebGL 관련 8개 속성: UNMASKED_VENDOR_WEBGL, UNMASKED_RENDERER_WEBGL, WEBGL_debug_renderer_info 등
-
화면 정보 8개: colorDepth, pixelDepth, width, height, availWidth, availHeight, availLeft, availTop
-
하드웨어 5개: hardwareConcurrency, deviceMemory, maxTouchPoints, platform, vendor
-
폰트 측정 4개: 숨겨진 div 생성 후 fontFamily, fontSize, getBoundingClientRect, innerText로 렌더링 크기 측정
-
DOM 탐색 8개: createElement, appendChild, removeChild, style, position, visibility, ariaHidden 등
-
저장소 5개: storage, quota, estimate, setItem, usage
- 결과를 localStorage의 키 6f376b6560133c2c에 저장하여 페이지 재로딩 간 지속
-
Layer 2: Cloudflare 네트워크
-
엣지 헤더 5개: cfIpCity, cfIpLatitude, cfIpLongitude, cfConnectingIp, userRegion
- 이 값들은 Cloudflare 네트워크를 통해서만 존재하며, 직접 오리진 서버에 접근하는 봇은 누락되거나 불일치 값 발생
-
Layer 3: 애플리케이션 상태
-
React 내부 구조 3개: __reactRouterContext, loaderData, clientBootstrap
- 이 항목들은 ChatGPT의 React 애플리케이션이 완전히 렌더링되고 SSR 하이드레이션이 완료된 경우에만 존재
- HTML만 로드하거나 JS 번들을 실행하지 않는 헤드리스 브라우저, 혹은 React를 실제로 실행하지 않는 봇 프레임워크는 실패
토큰 생성 과정
- 55개 속성 수집 후, 프로그램은 116바이트 암호화 블롭을 복호화하여 4개의 최종 명령어를 실행
-
JSON.stringify(fingerprint) → store → XOR(json, key) → RESOLVE
- 결과값은 OpenAI-Sentinel-Turnstile-Token 헤더로 변환되어 모든 대화 요청에 포함
Sentinel의 추가 구성 요소
-
Turnstile 외에도 두 가지 추가 검증 모듈 존재
-
Signal Orchestrator
- 271개 명령어로 구성
-
keydown, pointermove, click, scroll, paste, wheel 이벤트 리스너를 설치
-
window.__oai_so_* 속성 36개를 추적하여 키 입력 타이밍, 마우스 속도, 스크롤 패턴, 유휴 시간, 붙여넣기 이벤트 등을 모니터링
- 지문 수집 외에 행동 기반 생체 인증 계층 역할 수행
-
Proof of Work
-
25개 필드 지문 + SHA-256 hashcash 기반
- 난이도는 400K~500K 범위의 균등 난수, 72%가 5ms 이하로 해결
-
ai, createPRNG, cache, solana, dump, InstallTrigger, data 등 7개 이진 탐지 플래그 포함 (100개 샘플 모두 0)
- 연산 비용을 추가하지만 주요 방어 수단은 아님
토큰 복호화 가능 주체와 보안 의미
- 내부 프로그램의 XOR 키는 서버가 생성하여 바이트코드에 포함되므로, turnstile.dx를 생성한 서버만이 키를 알고 있음
- 사용자와 시스템 운영자 간의 프라이버시 경계는 암호학적 제약이 아닌 정책적 결정에 의해 정의됨
- 난독화의 목적은
- 지문 수집 항목을 정적 분석으로부터 숨김
- 웹사이트 운영자(OpenAI)가 원시 지문값을 직접 읽지 못하도록 차단
- 각 토큰을 고유하게 만들어 재사용(replay) 방지
- Cloudflare가 검사 항목을 변경해도 외부에서 인지하기 어렵게 함
- 그러나 암호화는 동일 데이터 스트림 내 키와 XOR 연산으로 이루어져 있어, 분석 방지 수준의 난독화에 불과함
수집 및 분석 통계
항목
값
| 복호화된 프로그램 |
377/377 (100%) |
| 관찰된 고유 사용자 |
32 |
| 프로그램당 속성 수 |
55 (모두 동일) |
| 명령어 수 |
417–580 (평균 480) |
| XOR 키 (50 샘플) |
41개 |
| Signal Orchestrator 속성 |
36개 |
| Proof of Work 필드 |
25개 |
| PoW 해결 시간 |
72%가 5ms 이하 |
분석 방법론
-
합법적 절차로 수집된 트래픽만 사용
- 개인 사용자 데이터는 공개되지 않음
- 모든 트래픽은 참여자 동의 하에 관찰
- Sentinel SDK(sdk.js, 1,411줄)를 수동 디옵스케이션 및 오프라인 복호화 수행
- 복호화는 Python을 이용해 오프라인 환경에서 진행
-
Homepage
-
개발자
- ChatGPT는 Cloudflare가 React 상태를 읽을 때까지 입력을 차단함