이미 고친 취약점이라던 그 자리에, URL 인코딩 하나만 다르게 해서 다시 들어갔다

이미 고친 취약점이라던 그 자리에, URL 인코딩 하나만 다르게 해서 다시 들어갔다

분류: Bugbounty · 웹·서버 · 계정탈취(ATO) [정보성]

덱. 보안 연구자 nokline은 ChatGPT의 “공유(share)” 기능을 살펴보다가, 이미 이전에 다른 연구자(Nagli)가 발견해 패치됐다고 알려졌던 웹 캐시 기만(WCD) 취약점이 URL 경로 순회(path traversal) 문자열을 살짝 다르게 인코딩하는 것만으로 다시 열린다는 걸 확인했습니다. 이 결함으로 사용자 인증 토큰을 캐시에서 훔쳐 계정을 탈취할 수 있었고, 원문에 따르면 연구자 본인 기준 최고액인 6,500달러가 지급됐습니다.

리드

CDN(콘텐츠 전송 네트워크)과 웹 서버가 같은 URL을 각자의 방식으로 해석할 때, 그 해석 차이(파서 불일치, parser differential)는 그 자체로 공격 표면이 됩니다. 특히 “패치했다”는 발표가 있었던 자리는 오히려 다시 살펴볼 가치가 있습니다 — 패치가 특정 인코딩 형태만 막고 그 뒤에 숨은 근본 원인(파서 간 불일치)은 그대로 남겨두는 경우가 드물지 않기 때문입니다. 이번 사례가 그 전형입니다.

핵심 사실 요약

  • ChatGPT는 사용자가 대화를 공개 링크로 공유할 수 있는 /share/{UUID} 기능을 갖고 있습니다. 연구자는 공유 링크가 새로 대화를 이어가도 갱신되지 않는 걸 이상하게 여겨 응답 헤더를 확인했고, Cf-Cache-Status: HIT(Cloudflare 캐시에서 응답됨)를 발견했습니다. 정적 파일도 아닌 페이지가 캐시되고 있었다는 뜻입니다.
  • /share/ 경로 뒤에 무엇이 와도(심지어 존재하지 않는 임의의 경로여도) 캐시가 적용된다는 걸 확인해, 캐시 규칙이 파일 확장자가 아니라 URL 경로의 접두사(/share/*) 하나만으로 걸려 있다는 걸 알아냈습니다. 이 규칙 자체는 위험 신호였습니다 — 접두사 기반 캐시 규칙은 그 뒤에 이어지는 경로가 실제로 어디를 가리키는지에 따라 완전히 다른 응답을 캐시해버릴 수 있기 때문입니다.
  • CDN을 거치는 구조에서는 URL이 두 번(CDN 단계, 웹 서버 단계) 파싱됩니다. 연구자는 이 두 단계가 URL 인코딩된 슬래시(%2F)를 서로 다르게 처리한다는 걸 확인했습니다. Cloudflare CDN은 %2F를 디코딩·정규화하지 않고 그대로 두는 반면, 원본 웹 서버는 이를 디코딩해 실제 경로 순회로 해석했습니다.
  • 이 차이를 이용해 https://chat.openai.com/share/%2F..%2Fapi/auth/session?cachebuster=... 같은 URL을 구성했습니다. CDN은 이 URL이 여전히 /share/ 접두사로 시작한다고 보고 캐시 대상으로 처리하지만, 웹 서버는 %2F..%2F를 디코딩해 실제로는 /api/auth/session — 사용자 인증 토큰이 담긴 API 엔드포인트 — 을 반환합니다. 결국 인증 토큰이 담긴 민감한 응답이 /share/...라는 겉보기 무해한 캐시 슬롯에 저장됐습니다.
  • 피해자가 공격자가 만든 이 URL을 한 번 방문하면 피해자의 인증 토큰이 그 캐시 슬롯에 저장되고, 공격자가 같은 URL을 다시 요청하면 캐시가 그 토큰을 그대로 반환합니다. 인증 토큰을 확보한 공격자는 피해자의 대화 내역, 청구 정보 등에 접근하며 사실상 계정을 탈취할 수 있었다고 원문은 밝히고 있습니다.
  • 원문은 이 취약점이 이전 연구자(Nagli)가 보고해 “패치됐다”고 알려졌던 WCD 결함과 같은 근본 매커니즘의 재발이라고 명시합니다. ⚠️미확인 — 정확한 신고·패치 일자, OpenAI 측 최종 대응 내역은 이 초안에서 1차출처(OpenAI 자체 공지·HackerOne 리포트 원문)로 별도 확증하지 않았습니다.

짧은 논평

이 사례는 “취약점이 패치됐다”는 발표를 어떻게 받아들여야 하는지에 대한 실무적인 교훈을 남깁니다. 최초 패치가 특정 페이로드(예: 디코딩 안 된 ../)만 걸러냈다면, 인코딩 형태를 바꾼 변형(%2F..%2F)이 같은 근본 결함을 다시 파고들 수 있습니다. 저희가 다른 글에서 짚은 필터 우회의 원칙과 정확히 같은 구조입니다 — 패치가 증상(특정 페이로드)을 막았는지, 원인(파서 간 해석 불일치)을 막았는지를 구분해야 한다는 것입니다. 캐시와 CDN이 개입하는 아키텍처에서는 이 원칙이 한 겹 더 무겁습니다. URL을 해석하는 주체가 CDN과 오리진 서버, 둘로 늘어나는 순간 “같은 URL을 두 컴포넌트가 다르게 읽는다”는 파서 불일치 자체가 상시적인 공격 표면이 되기 때문입니다.

References / 참고자료

  1. nokline, “How I was able to take over your account in ChatGPT” — 이 글이 재구성한 원 분석. 캐시 규칙 발견부터 URL 인코딩을 이용한 재발 재현까지 전 과정을 다룸. https://nokline.github.io/bugbounty/2024/02/04/ChatGPT-ATO.html

이 글은 위 원 분석을 우리 관점에서 다시 쓰고 짧은 논평을 더한 정보성 초안입니다. 특정 문장·요청 페이로드·스크린샷을 그대로 옮기지 않았으며, 작동하는 재현 절차는 싣지 않았습니다. ⚠️미확인 표시 항목은 발행 전 독립 팩트체크에서 확증·정정되어야 합니다.