Blog
innerHTML을 사용할 때 조심해야 하는 이유
서론#
JavaScript를 학습하면서 DOM을 직접 다루는 코드가 필요해 innerHTML을 사용했다.
처음에는 잘 동작했고, 크게 문제도 없어 보였다.
그런데 innerHTML은 사용에 주의해야 한다는 이야기를 자주 보게 됐고,
왜 그렇게까지 경계하는지 궁금해졌다.
이 글은 그 이유를 정리한 기록이다.
innerHTML이란?#
innerHTML은 요소 내부에 문자열을 넣으면,
그 문자열을 HTML로 해석해 기존 내용을 제거하고 DOM 구조를 다시 만드는 속성이다.
문자열만 넘기면 바로 화면을 구성할 수 있어서 편리하지만,
브라우저는 이 문자열이 어디서 왔는지까지는 판단하지 않는다.
innerHTML 사용 예제#
const div = document.createElement("div");
div.innerHTML = "<p>Hello <strong>World</strong></p>";
console.log(div.innerHTML);
// <p>Hello <strong>World</strong></p>
innerHTML의 문제점#
문제는 사용자 입력이 섞이는 순간이다.
const userInput = "<img src=x onerror=alert('XSS') />";
result.innerHTML = userInput;
이때 onerror는 이미지 로딩 실패 시 실행되는 이벤트 핸들러다.
이 경우, 브라우저는 문자열을 그대로 HTML로 해석하고
onerror에 들어 있는 스크립트까지 실행한다.
이처럼 사용자 입력을 그대로 해석하게 되면,
개발자가 의도하지 않은 스크립트가 실행될 수 있다.
XSS란?#
이런 방식으로 사용자 입력을 통해 스크립트가 실행되는 취약점을
XSS(Cross-Site Scripting)라고 부른다.
중요한 건 용어 자체보다도,
의도하지 않은 코드가 실행될 수 있다는 점이다.
가장 간단한 방지 방법#
HTML 구조가 필요 없는 경우라면
애초에 HTML로 해석되지 않게 만드는 것이 가장 안전하다.
result.textContent = userInput;
textContent는 태그를 문자열 그대로 취급하기 때문에
스크립트가 실행되지 않는다.
React에서는 왜 덜 보이는가?#
React는 JSX에 문자열을 렌더링할 때
기본적으로 HTML을 이스케이프 처리한다.
이 보호는 React가 대신 해주는 것이지,
브라우저의 innerHTML이 안전해진 것은 아니다.
<div>{userInput}</div>
이 방식에서는 문자열이 그대로 텍스트로 출력되기 때문에
innerHTML처럼 바로 XSS로 이어지지 않는다.
다만 다음과 같은 코드는 예외다.
<div dangerouslySetInnerHTML={{ __html: userInput }} />
이 경우에는 innerHTML과 동일한 위험을 갖는다.
sanitize란?#
sanitize는 입력값에서 위험한 태그나 속성을 제거해
안전한 HTML만 남기는 과정을 말한다.
innerHTML을 반드시 사용해야 하는 상황이라면
sanitize된 결과만 넣어야 한다.
대표적인 라이브러리로 DOMPurify가 있다.
import DOMPurify from "dompurify";
const userInput = "<img src=x onerror=alert('XSS') />";
const clean = DOMPurify.sanitize(userInput);
result.innerHTML = clean;
sanitize는 위험을 줄여주지만,
잘못된 설정이나 검증되지 않은 라이브러리 사용 시에는 여전히 취약할 수 있다.
그럼 언제 써도 되는가?#
다음과 같은 경우에는 상대적으로 안전하다.
- 서버에서 직접 생성한 HTML
- 마크다운을 파싱한 결과 (신뢰 가능한 파서와 sanitize를 거친 경우)
- 사용자 입력이 전혀 없는 정적 콘텐츠
반대로, 사용자가 만들 수 있는 값이 섞이면 사용하지 않는 것이 원칙이다.
정리#
innerHTML은 위험해서 쓰면 안 되는 API가 아니다.
다만 사용할 수 있는 조건이 매우 까다로운 API다.
사용자 입력이 섞이는 순간,
그 책임은 전부 개발자에게 넘어온다.
관련 게시글
5개
Next.js에서 보안 헤더를 설정한다는 것의 의미와 한계
Next.js에서 설정할 수 있는 보안 헤더들의 역할과 한계를 정리합니다. 보안 헤더가 무엇을 막아주지 않는지, 프레임워크와 개발자의 책임 경계를 중심으로 설명합니다.
React는 왜 기본적으로 XSS에 강할까?
React가 XSS에 강해 보이는 이유를 렌더링 방식 관점에서 설명하고, dangerouslySetInnerHTML, 속성 기반 주입, DOM 직접 조작 시 다시 취약해지는 지점을 정리합니다.
NEXT_PUBLIC_ 환경변수는 왜 env인데 클라이언트에 노출될까
NEXT_PUBLIC_ 환경변수가 왜 클라이언트 번들에 포함되는지, env는 언제 비밀이 되고 언제 설정값이 되는지 Next.js 빌드 구조와 보안 기준으로 정리합니다.
env는 보안인가? 많은 개발자들이 착각하는 이유
env는 보안 기능일까? 이 글에서는 env의 역할과 한계, NEXT_PUBLIC_ 환경 변수의 노출 특성, 프론트엔드에서 노출돼도 되는 값의 기준을 정리합니다.
XSS란 무엇인가: 브라우저에서 실행되는 공격
XSS(Cross-Site Scripting)는 서버가 아닌 사용자의 브라우저를 공격하는 웹 보안 취약점입니다. 이 글에서는 XSS의 개념, 발생 원인, 주요 유형과 위험성을 정리합니다.