레퍼런스6분 분량

HTML 엔티티 인코딩 vs URL 인코딩: 핵심 차이점

HTML 엔티티 인코딩은 `<`, `>`, `&`, `"` 같은 문자를 HTML 엔티티(`&lt;`, `&gt;`, `&amp;`, `&quot;`)로 변환하여 웹 페이지에서 안전하게 표시되도록 합니다. URL 인코딩은 문자를 `%XX` 형식으로 변환하여 URL에서 안전하게 전송되도록 합니다. 두 방식은 서로 다른 유형의 공격을 방어하며 서로 다른 맥락에서 사용됩니다.

HTML 엔티티 인코딩이란?

HTML 엔티티 인코딩(HTML 이스케이핑이라고도 함)은 HTML에서 특별한 의미를 갖는 문자를, 브라우저가 마크업으로 해석하지 않고 문자 그대로 표시하는 엔티티 참조로 변환하는 방식입니다. 인코딩해야 할 가장 중요한 문자는 <, >, &, ", '입니다.

HTML 인코딩을 하지 않으면 <script>alert('XSS')</script> 같은 문자열이 브라우저에서 JavaScript로 실행됩니다. 인코딩을 적용하면 &lt;script&gt;alert(&apos;XSS&apos;)&lt;/script&gt;로 바뀌어 무해한 텍스트로 표시됩니다.

HTML 엔티티는 두 가지 형태로 작성할 수 있습니다. &amp;, &lt;, &quot; 같은 명명된 엔티티(named entity)와 &#38;, &#60;, &#34; 같은 숫자 엔티티(numeric entity)입니다. 숫자 엔티티는 해당 문자의 유니코드 코드 포인트를 사용하므로 어떤 유니코드 문자든 표현할 수 있습니다.

<!-- HTML 인코딩 이전 -->
<p>The formula is: x < y & y > z</p>
<!-- 브라우저가 <를 태그 시작으로 해석합니다! -->

<!-- HTML 인코딩 이후 -->
<p>The formula is: x &lt; y &amp; y &gt; z</p>
<!-- 브라우저 표시 결과: x < y & y > z -->

<!-- 자주 쓰이는 HTML 엔티티 -->
&amp;   → &   (앰퍼샌드)
&lt;    → <   (보다 작음)
&gt;    → >   (보다 큼)
&quot;  → "   (큰따옴표)
&#39;   → '   (작은따옴표 / 아포스트로피)
&nbsp;  →     (줄바꿈 없는 공백)

URL 인코딩이란?

URL 인코딩(퍼센트 인코딩)은 URL에서 안전하지 않은 문자를, 퍼센트 기호 뒤에 바이트 값을 나타내는 두 자리 16진수를 붙인 형태로 변환하는 방식입니다. RFC 3986에 정의되어 있으며, URL이 인터넷을 통해 안전하게 전송될 수 있는 유효한 ASCII 문자만 포함하도록 보장합니다.

URL 인코딩이 필요한 이유는 URL이 엄격한 문법을 가지고 있고 특정 문자들(/, ?, &, #, =)이 구분자 역할을 하기 때문입니다. 이러한 문자가 데이터(예: 쿼리 파라미터 값) 안에 등장하면, 구조적 요소로 해석되지 않도록 반드시 인코딩해야 합니다.

// URL 인코딩 예시
space     → %20
&         → %26
=         → %3D
?         → %3F
#         → %23
/         → %2F
+         → %2B

// 인코딩된 쿼리 파라미터를 포함한 전체 URL
https://example.com/search?q=salt%20%26%20pepper
// q의 값은 "salt & pepper" 입니다

비교표

항목 HTML 엔티티 인코딩 URL 인코딩
목적 HTML 페이지에서 안전한 표시 URL에서 안전한 전송
표준 HTML/WHATWG 명세 RFC 3986
형식 &name; 또는 &#number; %XX (16진수 바이트 값)
공백 변환 &nbsp; (줄바꿈 없는 공백) 또는 그대로 유지 %20 또는 +
& 변환 &amp; %26
< 변환 &lt; %3C
방어 대상 XSS 공격, 깨진 HTML 깨진 URL, 인젝션 공격
사용 맥락 HTML 문서 본문 및 속성 URL 경로, 쿼리 문자열, 프래그먼트
JS 함수 내장 함수 없음 (라이브러리 또는 DOM API 사용) encodeURIComponent()

둘 다 필요한 경우는 언제인가?

HTML 인코딩과 URL 인코딩이 모두 필요한 상황이 흔히 있습니다. 가장 빈번한 경우는 hrefsrc 속성처럼 HTML 속성 안에 URL을 넣을 때입니다. 먼저 URL을 올바르게 URL 인코딩한 다음, 그 결과에 포함된 특수 HTML 문자를 HTML 인코딩해야 합니다.

<!-- 1단계: 쿼리 파라미터 값을 URL 인코딩 -->
<!-- 값: "Tom & Jerry" → URL 인코딩 결과: "Tom%20%26%20Jerry" -->

<!-- 2단계: URL 구성 -->
<!-- URL: https://example.com/search?q=Tom%20%26%20Jerry -->

<!-- 3단계: href 속성에 사용하기 위해 URL을 HTML 인코딩 -->
<a href="https://example.com/search?q=Tom%20%26%20Jerry">
  Search for Tom &amp; Jerry
</a>

<!-- 이 경우 % 시퀀스는 HTML 인코딩할 필요가 없습니다 -->
<!-- %는 특수 HTML 문자가 아니기 때문입니다 -->

<!-- 하지만 URL에 구분자로서 &가 포함되어 있다면 반드시 HTML 인코딩해야 합니다: -->
<a href="https://example.com/search?q=cats&amp;sort=name">
  Search cats sorted by name
</a>
<!-- &amp;를 쓰지 않으면 브라우저가 &sort를 HTML 엔티티로 해석할 수 있습니다 -->

React, Vue, Angular 같은 최신 프레임워크에서는 템플릿 표현식이나 JSX를 사용할 때 HTML 인코딩이 자동으로 처리됩니다. 그러나 동적 데이터로 URL을 구성할 때는 여전히 URL 인코딩을 명시적으로 수행해야 합니다.

// React - HTML 인코딩은 자동이지만 URL 인코딩은 자동이 아닙니다
function SearchLink({ query }) {
  // URL 인코딩은 명시적으로 해야 합니다
  const url = '/search?q=' + encodeURIComponent(query);

  // JSX에서 HTML 인코딩은 자동입니다
  return <a href={url}>Search for {query}</a>;
  // React가 href와 텍스트 콘텐츠를 모두 자동으로 HTML 인코딩합니다
}

XSS 방어: 어디에 어떤 인코딩을 사용할 것인가

크로스 사이트 스크립팅(XSS) 방어는 사용자 입력이 놓이는 맥락에 따라 올바른 인코딩을 적용해야 합니다. 특정 맥락에서 잘못된 유형의 인코딩을 사용하면 아무런 보호 효과가 없습니다.

  • HTML 본문 맥락: HTML 엔티티 인코딩을 사용합니다. 이를 통해 주입된 <script> 같은 태그가 마크업으로 해석되는 것을 막습니다.
  • HTML 속성 맥락: HTML 엔티티 인코딩을 사용하고 속성 값을 항상 따옴표로 감쌉니다. 따옴표로 감싸지 않은 속성은 HTML 인코딩을 하더라도 속성 범위를 벗어나는 공격이 가능합니다.
  • URL 맥락 (href, src): 동적 부분에 URL 인코딩을 적용한 다음, HTML에 넣을 경우 URL 전체를 HTML 인코딩합니다. javascript: URL을 막기 위해 URL이 안전한 스킴(https:)으로 시작하는지 검증하세요.
  • JavaScript 맥락: JavaScript 문자열 이스케이핑(JSON.stringify 또는 전용 라이브러리)을 사용합니다. JavaScript 맥락에서는 HTML 인코딩도 URL 인코딩도 충분하지 않습니다.
  • CSS 맥락: CSS 이스케이핑을 사용합니다. HTML 인코딩과 URL 인코딩은 CSS 인젝션을 막지 못합니다.

핵심 원칙은 이것입니다. 입력 출처가 아니라 출력 맥락에 맞게 인코딩하라. 하나의 사용자 입력이라도 응답 안에서 어디에 나타나느냐에 따라 서로 다른 인코딩이 필요할 수 있습니다. XSS 방어를 입력 검증에만 의존하지 말고, 항상 출력 인코딩을 함께 적용하세요.

관련 글

무료 도구 사용해 보기