Skip to content
블로그로 돌아가기
튜토리얼

HTML 엔티티 완벽 정리: 이름·숫자 참조와 이스케이프 시점

HTML 엔티티 실전 웹 가이드. 이름·10진·16진 참조의 차이, 반드시 이스케이프할 다섯 문자, XSS를 막는 컨텍스트별 이스케이프 규칙을 정리했습니다.

8 분 소요

HTML 엔티티는 어떤 문자를 마크업이 아니라 텍스트로 보여 주도록 적는 방법입니다. 콘텐츠에 <를 날것 그대로 입력하면 브라우저는 태그를 읽기 시작하지만, 대신 &lt;라고 적으면 화면에 리터럴 <가 그대로 렌더링됩니다. 이 맞바꿈이 바로 HTML 엔티티 인코딩의 핵심입니다.

HTML에서 특별한 의미를 갖는 문자는 다섯 개이며, 가장 자주 이스케이프하게 되는 문자도 이들입니다: <, >, &, ", '. 이스케이프하는 이유는 두 가지입니다. 첫째는 표시입니다. 코드나 마크업을 텍스트로 보여 주고 싶을 때입니다. 둘째는, 그리고 더 중요한 것은 보안입니다. 신뢰할 수 없는 입력을 이스케이프하는 것이 크로스 사이트 스크립팅(XSS)을 막는 토대이기 때문입니다.

어떤 엔티티든 적는 방법은 세 가지이며 서로 바꿔 쓸 수 있습니다. 이름(&lt;), 10진수(&#60;), 16진수(&#x3C;)가 그것이고, 셋 다 동일한 문자로 해석됩니다. 더 까다로운 질문은 언제 이스케이프하느냐와 무엇으로 이스케이프하느냐입니다. 정답은 값이 어디에 놓이느냐, 즉 HTML 텍스트인지 속성인지 스크립트인지 URL인지에 따라 달라지기 때문입니다. 이 가이드에서는 표기법, 예약 문자 집합, 컨텍스트별 결정 행렬, 그리고 사람들이 가장 자주 발을 헛디디는 함정을 차례로 살펴봅니다.

HTML 엔티티란? (구조)

문자 참조라고도 불리는 HTML 엔티티는 단일 문자를 대신하는 짧은 코드입니다. 모든 엔티티는 앰퍼샌드 &로 시작해 세미콜론 ;으로 끝납니다. 그 사이에 무엇이 들어가느냐에 따라 어떤 문자가 나오는지 결정됩니다.

형태는 세 가지입니다:

  • &name;&lt;&copy;처럼 이름 참조입니다.
  • &#decimal;&#60;처럼 10진수 숫자 참조입니다.
  • &#xhex;&#x3C;처럼 16진수 숫자 참조입니다.

브라우저는 참조를 읽고, 그것이 가리키는 문자를 찾아, 그 단일 문자를 렌더링합니다. 눈에 보이는 결과는 전혀 달라지지 않습니다. &lt;와 날것의 <는 똑같이 표시됩니다. 유일한 차이는 엔티티가 텍스트로 취급되며 결코 태그의 시작으로 읽히지 않는다는 점입니다.

세 가지 표기법: 이름, 10진수, 16진수

세 표기법 모두 같은 유니코드 코드 포인트를 참조하며, 차이는 철자뿐입니다. 이름 엔티티는 읽기 좋은 형태이지만, 정의된 이름이 있는 문자에 한해서만 존재합니다. 10진수 엔티티는 코드 포인트를 10진법으로 적습니다. 16진수 엔티티는 같은 코드 포인트를 16진법으로 적는데, 이는 유니코드 표준에서 보는 U+XXXX 표기와 일대일로 대응됩니다.

문자이름10진수16진수
<&lt;&#60;&#x3C;
&&amp;&#38;&#x26;
©&copy;&#169;&#xA9;
é&eacute;&#233;&#xE9;

16진수는 U+XXXX를 그대로 비추기 때문에(éU+00E9이고 따라서 &#xE9;입니다) 특정 코드 포인트를 문서화하거나 따져 볼 때 많은 개발자가 16진수를 택합니다. 일상적인 마크업에는 이름 엔티티가 가장 읽기 좋습니다.

반드시 이스케이프해야 할 다섯 예약 문자

이들은 브라우저가 문서를 파싱하는 방식을 바꾸는 HTML 특수 문자입니다. 실행이 아니라 표시되어야 할 콘텐츠에 이 중 하나가 나타나면 이스케이프하세요.

문자이름10진수16진수이스케이프하지 않으면 깨지는 것
<&lt;&#60;&#x3C;태그를 시작 — 뒤따르는 텍스트를 브라우저가 마크업으로 읽음
>&gt;&#62;&#x3E;태그를 너무 일찍 닫음
&&amp;&#38;&#x26;엔티티를 시작 — 나머지가 참조로 잘못 읽힐 수 있음
"&quot;&#34;&#x22;큰따옴표 속성 값을 일찍 끝냄
'&#x27;&#39;&#x27;작은따옴표 속성 값을 일찍 끝냄

HTML 앰퍼샌드 엔티티는 이 체계 전체의 뿌리입니다. & 문자는 모든 엔티티를 시작하므로 가장 먼저 이스케이프해야 합니다. 앰퍼샌드보다 꺾쇠 괄호를 먼저 이스케이프하면 방금 만들어 낸 엔티티 속의 &를 다시 이스케이프하게 됩니다. 이 함정은 아래에서 더 다룹니다.

실제로 언제 이스케이프해야 하나? (컨텍스트 기반)

대부분의 버그와 대부분의 취약점이 바로 여기에 도사립니다. 핵심 원칙은 짧습니다. 출력 시점에, 값이 놓이는 컨텍스트에 맞춰 이스케이프하세요. 한 곳에서 안전한 값이 다른 곳에서는 위험하므로, 적용하는 인코딩은 목적지와 일치해야 합니다.

HTML 요소 콘텐츠

<p>, <div>, <td> 안처럼 태그 사이에 값을 넣을 때는 <, >, &를 이스케이프하세요. 여기서 따옴표를 이스케이프해도 해롭지는 않지만 불필요합니다. <strong>이라는 텍스트를 다음 단어를 굵게 만드는 대신 리터럴 문자로 보여 주고 싶다면, &lt;strong&gt;로 인코딩하세요. 그러면 브라우저는 태그를 적용하는 대신 그대로 출력합니다.

HTML 속성 값

속성 안에서는 따옴표 문자가 결정적으로 중요해집니다. 값이 title="…" 안에 들어가는데 이스케이프되지 않은 "를 포함하면, 속성을 일찍 끝내고 공격자가 새 속성을 덧붙일 수 있게 됩니다. 전형적인 XSS 벡터입니다. 속성 컨텍스트에서는 "를 (그리고 가급적 '도) 이스케이프하세요. He said "hi" 같은 값은 안에 갇히도록 He said &quot;hi&quot;가 되어야 합니다.

<script> 또는 인라인 JavaScript 안

여기서는 HTML 엔티티가 도움이 되지 않습니다. <script> 블록이나 인라인 이벤트 핸들러에 들어가는 문자열은 문자 참조가 아니라 JavaScript 또는 JSON 문자열 이스케이프가 필요합니다. JS 문자열 리터럴 안에 &quot;를 적으면 따옴표가 아니라 리터럴 여섯 글자가 그대로 나옵니다. 이 컨텍스트에서는 JSON 이스케이프 도구를 사용하고, 스크립트 안에서 실제로 적용되는 \uXXXX 규칙은 JSON 문자열 이스케이프 완벽 가이드를 참고하세요.

URL 안

URL에는 자체 이스케이프 방식인 퍼센트 인코딩이 있습니다. HTML 엔티티는 값을 URL에 안전하게 만들어 주지 못합니다. a&b c 문자열은 쿼리에서 a&amp;b c가 아니라 a%26b%20c로 들어가야 합니다. 공백은 여전히 URL을 깨뜨리고, &는 여전히 매개변수를 구분하기 때문입니다. 이 작업에는 URL 인코더·디코더를 사용하고, 예약 문자와 비예약 문자에 대한 전체 규칙은 URL 인코딩·디코딩 가이드를 참고하세요.

결정 행렬

컨텍스트이스케이프 방법예시실패하는 잘못된 선택
HTML 요소 콘텐츠HTML 엔티티 (< > &)<strong>&lt;strong&gt;<를 날것으로 두면 태그가 주입됨
HTML 속성 값HTML 엔티티 (" '이 결정적)"hi"&quot;hi&quot;이스케이프되지 않은 "가 탈출함
<script> / 인라인 JSJS / JSON 문자열 이스케이프"\"HTML 엔티티는 JS에서 무효함
URL / 쿼리 문자열퍼센트 인코딩공백 → %20&amp;와 엔티티는 여전히 URL을 깨뜨림

이름 대 숫자: 어느 쪽을 써야 하나?

이름 엔티티는 읽기 좋으며, 흔한 예약 문자와 잘 알려진 기호(&lt;, &amp;, &copy;, &mdash;)에는 기본값으로 알맞습니다. 다만 정의된 이름이 있는 문자에 한해서만 존재합니다. 10진수든 16진수든 숫자 엔티티는 이름이 없는 것을 포함해 어떤 코드 포인트든 인코딩할 수 있으므로 보편적인 대체 수단이 됩니다. 소비하는 시스템이 특정 이름 엔티티를 지원하는지 장담할 수 없을 때는 숫자가 안전한 선택입니다.

작은따옴표가 &apos;가 아니라 &#x27;인 이유

이름 엔티티 &apos;는 HTML5와 XML에서야 도입되었습니다. HTML4에서는 정의되어 있지 않으므로, 일부 오래된 파서와 이메일 클라이언트는 이를 작은따옴표가 아니라 리터럴 텍스트 &apos;로 렌더링합니다. 숫자 참조 &#x27;은, 그리고 그 10진수 짝인 &#39;도 똑같은 문자 U+0027을 가리키며, 지금까지 작성된 모든 표준 준수 파서가 이를 이해합니다. he 같은 잘 검증된 이스케이프 라이브러리가 작은따옴표에 &#x27;을 내보내는 것도 바로 이 이유 때문이며, 좋은 인코더는 출력이 어떤 HTML, XML, 속성 컨텍스트에 넣어도 안전하도록 이 관례를 따릅니다.

문자 집합 대 엔티티: 비-ASCII는 언제 인코딩하나

UTF-8 같은 문자 집합은 문자를 바이트로 어떻게 저장할지 정합니다. 엔티티는 순수 ASCII(&, #, ;, 글자, 숫자)만으로 문자를 적는 방법입니다. 이 둘은 서로 다른 계층이며, 둘을 뒤섞으면 불필요한 인코딩으로 이어집니다.

UTF-8 페이지에서는, 즉 <meta charset="utf-8">을 선언하는 거의 모든 현대 페이지에서는 강세 부호가 붙은 글자, 대시, 이모지가 유효한 날것 그대로의 문자입니다. é, , 😀는 있는 그대로 두세요. 모든 것을 엔티티로 인코딩하는 일이 의미를 갖는 경우는 텍스트가 레거시 단일 바이트 문자 집합이나 날것의 UTF-8을 망가뜨리는 시스템을 거쳐 살아남아야 할 때뿐입니다. 그런 경우를 위해 “모든 비-ASCII 인코딩” 모드가 존재합니다. 바이트, 코드 포인트, 문자가 어떻게 관계 맺는지 확실하지 않다면 UTF-8, UTF-16, 유니코드 인코딩 가이드가 그 모델을 정리해 줍니다.

흔한 HTML 엔티티 함정

&를 마지막에 이스케이프하면 이중 이스케이프가 발생한다

순서가 중요합니다. <>&보다 먼저 치환하면, 방금 만든 엔티티(&lt;, &gt;)의 앞쪽 &까지 이스케이프되어 <가 결국 &amp;lt;가 되고 리터럴 텍스트 &lt;로 렌더링됩니다. 항상 &를 먼저 이스케이프하고 나머지를 이스케이프하세요. 이 한 가지 규칙만으로 가장 흔한 인코딩 버그를 막을 수 있습니다.

이미 이스케이프된 텍스트를 이중 인코딩하기

이미 이스케이프된 텍스트를 인코더에 다시 넣으면 또 한 번 인코딩됩니다. &amp;&amp;amp;가 되고, 방문자는 페이지에서 & 대신 &amp;를 보게 됩니다. 이스케이프는 출력 시점에 정확히 한 번만 하세요. 값이 여러 계층을 거친다면 그중 한 계층에서만 이스케이프하도록 하세요.

디코딩할 때 발생하는 모지바케

반대 방향에도 나름의 함정이 있습니다. 잘못된 문자 집합으로 디코딩하거나 두 번 디코딩하면 깨진 출력, 즉 전형적인 모지바케가 나옵니다. <가 나와야 할 자리에 리터럴 &amp;lt;가 보인다면, HTML 엔티티 디코더에 붙여 넣어 엔티티가 정확히 무엇으로 해석되는지 확인하세요. 이 도구는 이름, 10진수, 16진수는 물론 세미콜론이 없는 &copy 같은 레거시 미종결 참조까지 처리합니다.

이스케이프를 XSS 완전 치료제로 믿기

이스케이프는 첫 번째 방어선일 뿐 유일한 방어선은 아닙니다. HTML에는 규칙이 서로 다른 여러 컨텍스트가 있으므로, 엉뚱한 컨텍스트용으로 이스케이프하면 구멍이 남습니다. 속성의 따옴표, 스크립트의 JS 이스케이프, URL의 퍼센트 인코딩이 그렇습니다. 올바른 컨텍스트 기반 이스케이프를 콘텐츠 보안 정책(CSP) 및 프레임워크의 자동 이스케이프와 짝지으세요. 엔티티 인코딩을 토대로 삼고, 그 위에 CSP와 프레임워크 기본값을 겹쳐 올린다고 생각하세요.

실전에서 엔티티를 인코딩·디코딩하는 방법

HTML을 직접 손으로 만들 때는 이스케이프도 직접 해야 합니다. 다음은 & 우선 순서를 처리하는 올바른 escapeHtml()이며, 실제 애플리케이션 코드를 위한 더 나은 방식도 함께 보여 줍니다.

// 다섯 예약 문자와 그에 대응하는 안전한 엔티티:
//   <  →  &lt;     >  →  &gt;     &  →  &amp;     "  →  &quot;     '  →  &#x27;

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')   // &를 먼저, 그래야 뒤의 엔티티가 이중 이스케이프되지 않음
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;'); // 숫자 형태 — HTML4, HTML5, XML에서 안전
}

const userInput = `<a href="x">Tom & Jerry's</a>`;
const safe = escapeHtml(userInput);
// → &lt;a href=&quot;x&quot;&gt;Tom &amp; Jerry&#x27;s&lt;/a&gt;

// 앱 코드에서는 더 나은 방법: 플랫폼이 대신 이스케이프하게 하라.
//   el.textContent = userInput;   // 브라우저가 이스케이프; 수동 치환 불필요
//   React / Vue / Angular는 보간된 텍스트를 기본으로 이스케이프함
//   서버 템플릿(Jinja, ERB, Blade)은 명시적으로 끄지 않는 한 자동 이스케이프함

손수 만든 함수는 무슨 일이 일어나는지 이해하거나 일회성 변환을 할 때 유용하지만, 프로덕션에서는 내장 경로를 우선하세요. element.textContent를 설정하면 브라우저가 대신 이스케이프해 주고, 현대 프레임워크는 보간된 값을 자동으로 이스케이프합니다. 수동 이스케이프는 플랫폼이 다뤄 주지 않는 경우에만 아껴 두세요.

즉석 작업에는 HTML 엔티티 인코더가 예약 문자 집합을 (이름, 10진수, 또는 16진수로) 이스케이프하고, HTML 엔티티 디코더가 이를 되돌립니다. 둘은 예약 문자에 대해 정확한 역연산이므로, 텍스트를 손실 없이 두 도구를 거쳐 왕복시킬 수 있습니다.

자주 묻는 질문

HTML 엔티티란 무엇인가요?

HTML 엔티티는 &로 시작해 ;으로 끝나며 단일 문자를 나타내는 짧은 코드입니다. 브라우저는 엔티티를 마크업으로 취급하는 대신 그것이 가리키는 문자를 렌더링합니다. 예를 들어 &lt;는 리터럴 <를, &amp;는 리터럴 &를 표시합니다.

HTML에서 어떤 문자를 이스케이프해야 하나요?

다섯 개의 예약된 HTML 특수 문자입니다: <, >, &, ", '. 요소 콘텐츠에서는 주로 <, >, &가 필요하고, 속성 값에서는 따옴표 "'도 결정적으로 중요해집니다. 다른 엔티티가 이중 이스케이프되지 않도록 앰퍼샌드 &를 가장 먼저 이스케이프하세요.

이름 엔티티와 숫자(10진수/16진수) 엔티티 중 무엇을 써야 하나요?

흔한 문자에는 알아보기 쉬운 이름 엔티티(&lt;, &copy;)를 가독성을 위해 사용하세요. 정의된 이름이 없는 문자를 인코딩해야 하거나 소비자가 특정 이름 엔티티를 지원하는지 장담할 수 없을 때는 숫자 엔티티(10진수 &#60; 또는 16진수 &#x3C;)를 사용하세요. 두 형태 모두 같은 코드 포인트를 참조합니다.

HTML 엔티티는 XSS로부터 보호하나요?

올바르게 적용하면 그것이 토대가 됩니다. 신뢰할 수 없는 입력을 HTML 요소나 속성 콘텐츠에 넣기 전에 다섯 예약 문자를 이스케이프하면 태그 및 스크립트 주입을 막을 수 있습니다. 다만 이스케이프는 컨텍스트에 의존합니다. 스크립트 블록에는 JavaScript 이스케이프가, URL에는 퍼센트 인코딩이 필요합니다. 올바른 컨텍스트 기반 이스케이프를 CSP 및 프레임워크 자동 이스케이프와 결합하세요.

페이지에 < 대신 &amp;lt;가 보이는 이유는 무엇인가요?

이중 이스케이프 때문입니다. 텍스트가 두 번 인코딩되었거나, 꺾쇠 괄호 뒤에 &를 이스케이프하는 바람에 &lt;&&amp;로 바뀐 것입니다. 그러면 방문자는 &lt;를 리터럴 텍스트로 보게 됩니다. 이스케이프는 정확히 한 번만 하고 항상 &를 먼저 이스케이프하세요. 디코더 도구로 엔티티가 무엇으로 해석되는지 확인할 수 있습니다.

é, — 또는 이모지 같은 문자도 이스케이프해야 하나요?

보통은 아닙니다. <meta charset="utf-8">을 선언하는 페이지에서는 강세 부호가 붙은 글자, 대시, 이모지가 유효한 날것 그대로의 문자이므로 인코딩이 필요 없습니다. 있는 그대로 두세요. 텍스트가 레거시 단일 바이트 문자 집합이나 날것의 UTF-8을 손상시키는 시스템을 거쳐야 할 때만 비-ASCII를 인코딩하세요.

태그: HTML Encoding Security Web