외부 사이트를 안전하게 미리보기하기 — 프록시 + 새니타이즈 + iframe sandbox 구현
- engineering
- security
- nextjs
- iframe
이 글의 요약
- 외부 사이트를 자사 오리진의 iframe에 로드하면, 외부 JS가 자사 세션을 건드릴 수 있는 위험이 있다
- 대책은 "SSRF 방어가 적용된 프록시 + 외부 JS 제거 + 제한된 CSP + iframe sandbox"의 다층 방어
- sandbox는 너무 강하게 설정하면 자체 스크립트까지 막아버린다.
allow-scripts를 빠뜨려 기능이 동작하지 않는 함정도 있었다
HeatMapX의 A/B 테스트에는 "변경 후(Variant B)의 모습을 대상 페이지에 적용한 상태로 미리보기"하는 기능이 있습니다. 이는 기술적으로 보면 "사용자의 외부 사이트를 자사 도메인의 iframe 안에서 안전하게 표시한다"는, 의외로 까다로운 처리입니다. 설계와 실제로 겪었던 함정을 공유합니다.
왜 단순한 구현이 위험한가
외부 사이트의 HTML을 그대로 가져와 자사 오리진(예: heatmapx.com)에서 서빙하면, 그 HTML에 포함된 <script>나 onclick= 등이 자사 오리진의 권한으로 실행되어 버립니다. 그러면 쿠키나 로그인 세션이 읽힐 위험이 있습니다. 이는 반드시 피해야 합니다.
다층 방어 구성
그래서 다음과 같은 계층을 겹쳐 적용하고 있습니다.
1. SSRF 방어가 적용된 프록시
가져올 URL을 검증하여 http/https 이외는 거부합니다. 나아가 localhost·사설 IP·클라우드 메타데이터 엔드포인트(169.254.169.254 등)를 차단합니다. 리다이렉트도 자동으로 따라가지 않고 redirect: 'manual'로 한 단계씩 재검증하여, 내부 네트워크로 유도되지 않도록 합니다.
2. 외부 JS 제거(새니타이즈)
가져온 HTML에서 <script> 태그와 on*= 인라인 핸들러, javascript: 스킴을 제거합니다. 미리보기에서 고객 사이트의 JS를 동작시킬 필요가 없기 때문입니다(SPA를 완전히 재현할 수 없는 것은 알려진 트레이드오프입니다).
3. 제한된 CSP와 base href
<head> 바로 뒤에 <base href>를 삽입해 상대 경로의 이미지·CSS를 해석하면서, 응답에는 "외부 JS는 동작시키지 않는" 제한된 CSP를 부여합니다. 이미지·CSS는 표시를 위해 허용하고 있습니다.
4. iframe sandbox
마지막으로, 표시 측 iframe에 sandbox를 붙여 격리합니다.
겪었던 함정: sandbox가 너무 강했다
미리보기는 가져온 HTML에 "변경사항(changeSet)을 적용하는 작은 자체 스크립트"를 주입하고, iframe 안에서 실행해 모습을 바꿉니다. 그런데 미리보기용 iframe의 sandbox가 allow-same-origin뿐이고, allow-scripts가 빠져 있었습니다.
그 결과, 자체 스크립트도 실행되지 않아 변경사항이 적용되지 않은 채 원본 페이지가 표시되는 문제가 발생했습니다. 편집용 에디터 측 iframe은 allow-scripts allow-same-origin으로 올바르게 동작하고 있었기 때문에, 설정 간의 불일치가 원인이었습니다.
수정은 간단합니다. 미리보기 쪽도 allow-scripts allow-same-origin으로 맞추기만 하면 됩니다. 아울러 iframe의 sandbox 속성을 검증하는 회귀 테스트를 추가했습니다.
sandbox="allow-same-origin" → sandbox="allow-scripts allow-same-origin"
배운 점
- 외부 콘텐츠 삽입은 "다층 방어"로 생각한다. 하나의 대책에만 의존하지 않는다.
sandbox는 "붙이면 안전하다"가 아니라, 필요한 권한만 정확하게 허용하는 것이 핵심이다. 너무 강하면 자기 기능까지 멈춘다.- "같은 처리를 하는 두 개의 경로"(에디터와 미리보기)는 보안 속성을 맞췄는지 반드시 확인한다. 한쪽만 고치면 다른 한쪽이 깨진다.
정리
외부 사이트의 안전한 미리보기는 SSRF 방어·외부 JS 제거·제한된 CSP·iframe sandbox의 다층 방어로 실현할 수 있습니다. 그리고 sandbox의 권한은 부족해도, 너무 많아도 문제가 됩니다. 이번의 allow-scripts 누락은 그 전형적인 예였습니다.