SVG 미리보기에서 script 태그가 실행 안 되는 이유 — img+Blob 방식의 안전장치
SVG는 이미지 포맷이면서 동시에 XML 문서입니다. 그리고 XML 문서 안에는 <script> 태그와 onload, onclick 같은 이벤트 핸들러 속성을 넣을 수 있습니다. 즉 SVG 파일 하나가 사실상 작은 웹페이지처럼 동작할 수 있다는 뜻이고, 이건 출처를 모르는 SVG를 함부로 열었을 때 실제로 악용될 수 있는 잘 알려진 XSS(크로스사이트 스크립팅) 공격 벡터입니다. 그런데 이 사이트의 SVG 뷰어를 포함한 대부분의 안전한 SVG 미리보기 도구는 스크립트가 든 SVG를 붙여넣어도 아무 일도 일어나지 않습니다. 왜 그럴까요?
1. SVG는 원래 스크립트를 실행할 수 있는 포맷이다
브라우저 주소창에 SVG 파일을 직접 열거나, HTML 문서 안에 <object>·<iframe>·<embed> 태그로 SVG를 삽입하면, 그 SVG는 완전한 독립 문서(document context)로 취급됩니다. 이 경우 내부의 <script> 태그와 이벤트 핸들러가 그대로 실행됩니다. 마찬가지로 SVG 마크업 문자열을 innerHTML로 DOM에 직접 삽입해도 스크립트가 실행됩니다. 사용자가 붙여넣은 SVG 코드를 이런 방식으로 렌더링하는 도구는 그 자체로 XSS 공격에 노출됩니다.
2. 이 도구가 쓰는 방어 기법: img + Blob
이 사이트의 SVG 뷰어는 입력된 코드를 먼저 DOMParser로 파싱해 문법 오류(<parsererror>)가 있는지 검사합니다. 파싱에 성공하면, 원본 코드 문자열을 그대로 Blob(MIME 타입 image/svg+xml)으로 감싸고, URL.createObjectURL()로 만든 임시 URL을 <img> 태그의 src에 넣어 표시합니다. 핵심은 여기에 있습니다 — 브라우저는 <img> 태그로 로드된 리소스를 "정적 이미지 컨텍스트"로 취급하도록 스펙에 정의돼 있고, 이 컨텍스트에서는 그 이미지가 SVG(XML)라 하더라도 내부의 스크립트나 이벤트 핸들러가 절대 실행되지 않습니다. 이건 우연히 그렇게 동작하는 게 아니라, 브라우저 벤더들이 의도적으로 정한 보안 경계입니다.
<img src="blob:...">로 불러온 SVG는 렌더링(모양을 그리는 것)만 되고, 그 안의 코드가 "실행"되는 문서 컨텍스트 자체가 아예 생성되지 않습니다. img로 SVG를 로드하는 것은 사실상 png·jpg 같은 래스터 이미지를 로드하는 것과 동일한 보안 취급을 받습니다.
3. 반대로 위험한 방식들
| 렌더링 방식 | 스크립트 실행 여부 |
|---|---|
<img src="..."> (Blob URL / data URI) | ❌ 실행 안 됨 — 안전 |
element.innerHTML = svgCode | ✅ 실행됨 — 위험 |
<object data="..." type="image/svg+xml"> | ✅ 실행됨 — 위험 |
<iframe src="..."> | ✅ 실행됨 — 위험 (단, cross-origin 격리는 있음) |
CSS background-image: url(...) | ❌ 실행 안 됨 — 안전 |
<object>·<iframe>은 SVG를 완전한 독립 문서로 취급하기 때문에 스크립트가 살아납니다. 반면 <img>와 CSS background-image는 SVG를 순수한 그림으로만 취급합니다. 사용자가 업로드하거나 붙여넣은 SVG를 미리보기로 보여줘야 하는 웹서비스를 만든다면, 반드시 <img> 방식을 써야 합니다.
4. 줌 기능이 화질 저하 없이 동작하는 이유(덤)
이 도구의 줌 기능은 이미지를 다시 렌더링하는 게 아니라 CSS transform:scale()로 이미 표시된 요소를 확대·축소합니다. SVG는 픽셀이 아니라 수학적 경로(path)로 정의된 벡터 포맷이라, 4배까지 확대해도 래스터 이미지처럼 계단현상(pixelation)이 생기지 않고 선명하게 유지됩니다.
5. 인라인 SVG가 꼭 필요한 경우엔?
애니메이션 상호작용이나 CSS 스타일링이 SVG 내부 요소 단위로 필요해서 어쩔 수 없이 인라인으로 삽입해야 하는 경우도 있습니다. 이럴 땐 img 방식을 포기하는 대신, DOMPurify 같은 라이브러리의 SVG 프로필(USE_PROFILES: {svg: true})로 <script>·이벤트핸들러·<foreignObject> 등 위험 요소를 제거한 뒤 삽입하는 방식이 표준적인 대안입니다.
자주 묻는 질문
Q. SVG 안에 스크립트 태그가 있으면 실행되나요?
A. 이 도구에서는 실행되지 않습니다. 파싱에 성공한 코드를 Blob으로 감싸 <img> 태그의 src로 표시하는데, 브라우저는 이미지로 로드된 SVG의 스크립트와 이벤트 핸들러를 원천적으로 차단합니다.
Q. CSS 애니메이션이나 SMIL 애니메이션이 든 SVG도 재생되나요?
A. 네. img 태그로 표시되는 SVG는 스크립트만 차단될 뿐, CSS 애니메이션과 SMIL(<animate> 등) 애니메이션은 이미지 자체의 렌더링 기능이라 정상적으로 재생됩니다.
Q. 그럼 이 방식이면 100% 안전한가요?
A. 스크립트 실행이라는 관점에서는 안전합니다. 다만 img+Blob 방식이 XSS는 막아주지만, 외부 리소스를 참조하는 SVG(<image xlink:href="외부URL"> 등)의 경우 그 리소스 요청 자체는 발생할 수 있어, 극도로 민감한 서비스라면 별도의 콘텐츠 검증이 추가로 필요할 수 있습니다.
Q. innerHTML로 SVG를 넣는 도구는 다 위험한가요?
A. 사용자가 직접 입력한 SVG를 검증·정화(sanitize) 없이 innerHTML로 그대로 삽입하는 도구라면 위험합니다. 다만 서비스가 자체 생성한(사용자 입력이 섞이지 않은) SVG를 삽입하는 경우라면 위험이 없습니다 — 위험은 "신뢰할 수 없는 입력"을 그대로 실행 가능한 컨텍스트에 넣을 때 발생합니다.