CDN 캐시 포이즈닝의 작동 원리

CDN 캐시 포이즈닝은 CDN을 속여서 악성 응답을 캐시하게 하고 모든 후속 방문자에게 서빙하게 한다. 이상한 요청 하나, 대량의 부수 피해.

캐시 포이즈닝은 캐시가 모두 앞에 앉아있을 때 훨씬 재미있어진다.

CDN 캐시 포이즈닝이 그렇게 골치 아픈 버그 클래스인 이유다. 브라우저 하나나 프록시 하나를 속이는 게 아니다. 공유 엣지 레이어가 공격자 제어 입력으로 형성된 응답을 저장하고, 같은 캐시 키에 매핑되는 모든 후속 방문자에게 그 포이즈닝된 응답을 재생하게 하는 것이다.

이상한 요청 하나. 대량의 부수 피해.

CDN이 하고 있다고 생각하는 것

CDN이 응답을 캐시해서 매번 오리진에 같은 것을 묻지 않는다. 더 빠른 페이지, 적은 오리진 부하. 요청이 들어오면 CDN이 캐시 키를 계산한다 — 보통 URL 경로, 쿼리 스트링, 일부 헤더에서 파생 — 그 키에 캐시된 응답이 있는지 확인한다. 있으면 캐시를 서빙. 없으면 오리진에 포워딩, 응답을 캐시, 서빙.

보안 가정: 같은 캐시 키를 가진 두 요청은 같은 응답을 만들어야 한다.

공격: 오리진은 응답을 생성하는 데 쓰지만 CDN이 캐시 키에 포함시키지 않는 입력을 찾는다. 그 입력을 조작한다. 오리진이 포이즈닝된 응답을 돌려준다. CDN이 정상 캐시 키 아래 캐시한다. 나머지 모두가 포이즈닝된 버전을 받는다.

캐시 키가 전부다

캐시 키는 CDN의 “같은 요청” 정의다. 두 요청이 같은 캐시 키를 만들면 CDN이 교환 가능하게 취급한다.

전형적 캐시 키는 URL 경로와 쿼리 스트링을 포함한다. 대부분의 HTTP 헤더는 보통 포함하지 않는다. 격차가 사는 곳이 여기다.

오리진 서버가 응답 생성 결정에 헤더를 쓸 수 있다. CDN이 그 헤더를 캐시 키에 포함시키지 않을 수 있다. 헤더가 “키 없음(unkeyed)” — 응답에는 영향을 미치지만 어떤 캐시 항목에 저장되는지에는 영향 안 미치는.

Unkeyed 헤더 공격

James Kettle의 웹 캐시 포이즈닝 기초 연구(Black Hat 2018 발표 이후 확장)가 이 패턴을 명확히 보여줬다.

X-Forwarded-Host를 생각해보자. 일부 오리진 서버가 이 헤더를 써서 응답에 절대 URL을 생성한다 — 리다이렉트, 캐노니컬 태그, 리소스 링크, OpenGraph 메타데이터. 오리진이 CDN이나 프록시가 올바르게 설정할 거라 믿고 헤더를 신뢰한다.

공격자가 X-Forwarded-Host: evil.com으로 요청을 보낸다. 오리진이 <script src="https://evil.com/payload.js">를 포함하는 응답을 생성한다. CDN이 X-Forwarded-Host를 캐시 키에 포함시키지 않는다 — URL의 일부가 아니니까. CDN이 /page의 정상 캐시 키 아래 이 포이즈닝된 응답을 캐시한다.

/page를 요청하는 모든 후속 방문자가 캐시 버전을 받는다 — 공격자의 주입된 콘텐츠와 함께. 공격자의 요청은 오래전에 사라졌다. 독이 캐시에 산다.

변주

X-Forwarded-Host 공격이 클래식이지만 패턴은 모든 unkeyed 입력에 적용된다:

X-Original-URL / X-Rewrite-URL. 일부 웹 프레임워크가 이 헤더로 요청 경로를 오버라이드하게 한다.

쿼리 파라미터 오염. 일부 CDN이 캐시 키에서 특정 쿼리 파라미터를 정규화하거나 무시한다.

Host 헤더 조작. 오리진이 Host 헤더를 URL 구성에 쓰고 CDN이 검증하지 않으면.

왜 방어가 어려운가

근본 원인은 CDN이 “같은 요청”으로 보는 것과 오리진이 “같은 요청”으로 보는 것의 불일치다. 응답에 영향 미치지만 캐시 키에 없는 모든 입력이 잠재적 공격 표면이다.

고치려면 오리진이 응답 생성에 쓰는 모든 입력을 알아야 한다. 실제로는 어렵다:

  • 설정하지 않은 헤더를 프레임워크가 추가
  • 몰랐던 헤더에서 미들웨어가 컨텍스트 주입
  • 서드파티 코드가 분석이나 라우팅용으로 헤더 읽음
  • CDN 문서가 캐시 키 동작을 완전히 명시하지 않음

방어

응답에 영향 미치는 모든 입력을 캐시 키에 포함시켜라. Vary 헤더가 이 용도로 존재한다.

오리진에서 포워딩 헤더를 신뢰하지 마라. X-Forwarded-Host를 쓴다면 허용 목록 대조 검증.

CDN 엣지에서 미지의 헤더를 제거해라. 오리진이 필요하지 않은 헤더가 도달하지 않으면 응답에 영향 못 미친다.

방어적으로 캐시해라. 사용자 제어 콘텐츠가 본문에 포함된 응답을 캐시하지 마라.

불편한 진실

대부분의 CDN 사용자가 캐시 키 설정을 감사하지 않는다. 캐싱을 켜고 기본값을 수락하고 CDN이 보안을 처리한다고 가정한다. CDN은 오리진이 보안을 처리한다고 가정한다. 아무도 격차를 감사하지 않는다.

CDN은 성능 레이어만이 아니다. 공격 표면이다. 캐시한 것을 질문 없이 모두에게 서빙하는.

토론 참여

← 블로그로 돌아가기