“HTML Escape”, XSS를 막는 가장 기본적인 방법
들어가며
웹 개발을 하다 보면 사용자에게 입력받은 내용을 다시 화면에 보여줘야 하는 경우가 정말 많습니다.
게시판 제목, 댓글, 검색어, 회원 이름, 문의 내용 등 생각보다 많은 데이터가 사용자의 입력으로 만들어집니다.
그런데 여기서 한 가지 문제가 생깁니다.
사용자가 입력한 문자열을 HTML에 그대로 넣어도 괜찮을까?
예를 들어 사용자가 게시판 제목에 다음과 같은 문자를 입력했다고 생각해보겠습니다.
<script>alert('XSS')</script>
개발자가 이것을 단순한 문자열이라고 생각하고 화면에 그대로 출력하면 브라우저는 상황에 따라 이것을 일반적인 글자가 아니라 HTML 코드로 해석할 수 있습니다.
이런 문제를 방지하기 위해 사용하는 대표적인 방법이 HTML Escape입니다.
HTML Escape는 HTML에서 특별한 의미를 가지고 있는 문자를 안전한 문자 형태로 변환해서 브라우저가 HTML 태그나 코드로 해석하지 않도록 만드는 방법입니다.
HTML Escape란?
HTML에는 특별한 의미를 가지는 문자들이 있습니다.
대표적으로 다음과 같은 문자입니다.
< >
& "
'
예를 들어 <와 >는 HTML 태그를 표현할 때 사용합니다.
<h1>제목</h1>
여기서 <h1>을 단순한 텍스트로 보여주고 싶다면 그대로 HTML에 넣어서는 안 됩니다.
다음처럼 Escape된 형태로 표현할 수 있습니다.
<h1>제목</h1>
브라우저에서는 이것을 HTML 태그로 실행하는 것이 아니라 다음과 같은 문자열 자체로 표시할 수 있습니다.
<h1>제목</h1>
즉, HTML Escape의 핵심은 간단합니다.
HTML에서 특별한 의미를 가지는 문자를 브라우저가 코드로 해석하지 못하도록 안전한 형태로 변환하는 것입니다.
가장 많이 보는 HTML Escape 문자
웹 개발을 하다 보면 다음과 같은 HTML Entity를 자주 볼 수 있습니다.
| 원래 문자 | HTML Escape |
|---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
예를 들어 다음 문자열이 있다고 해보겠습니다.
<script>alert("Hello")</script>
이를 HTML Escape하면 개념적으로 다음과 같이 바뀝니다.
<script>alert("Hello")</script>
이제 브라우저가 <script>를 실제 태그로 인식하기 어려워지고, 사용자에게는 입력한 문자열을 그대로 보여줄 수 있습니다.
HTML Escape와 XSS의 관계
앞에서 살펴본 반사형 XSS와 HTML Escape는 밀접한 관계가 있습니다.
XSS의 대표적인 원인 중 하나가 사용자 입력값을 적절하게 처리하지 않고 HTML 문맥에 그대로 출력하는 것입니다.
예를 들어 서버에서 다음과 같이 HTML을 만들었다고 해보겠습니다.
String keyword = request.getParameter("keyword");
String html =
"<p>검색어 : " + keyword + "</p>";
정상적인 검색어라면 문제가 없어 보입니다.
검색어 : 축구
하지만 사용자 입력이 HTML 구조에 영향을 줄 수 있는 값이라면 문제가 발생할 수 있습니다.
따라서 단순히 문자열을 연결하는 것보다는 출력되는 문맥에 맞는 안전한 인코딩 또는 Escape 처리를 적용하는 것이 중요합니다.
HTML Escape는 무조건 모든 것을 바꾸는 걸까?
여기서 처음 HTML Escape를 접하면 이런 생각을 할 수 있습니다.
“그럼 <, > 같은 문자는 전부 바꿔버리면 되는 것 아닌가?”
방향은 맞지만 실제 웹 개발에서는 조금 더 신경 써야 합니다.
왜냐하면 데이터가 어디에 들어가는지에 따라 필요한 처리가 달라지기 때문입니다.
예를 들어 HTML 본문에 들어가는 데이터와 HTML 속성에 들어가는 데이터는 상황이 다릅니다.
<p>사용자 이름</p>
와
<input value="사용자 이름">
은 같은 HTML이라도 데이터가 들어가는 위치가 다릅니다.
또한 JavaScript 코드 내부나 URL, CSS 문맥 등에서도 각각 고려해야 할 보안 문제가 있습니다.
그래서 보안에서는 단순히 “특수문자를 전부 제거한다”는 방식보다 출력 문맥에 맞는 인코딩(Context-Aware Encoding)을 적용하는 것이 중요합니다.
입력값을 삭제하는 것과는 다르다
HTML Escape를 입력값 필터링과 혼동하는 경우가 있습니다.
예를 들어 사용자가 입력한 < 문자를 무조건 삭제한다고 생각해보겠습니다.
10 < 20
사용자는 정상적인 문장을 입력했는데 <가 사라지면 데이터 자체가 변경됩니다.
반면 HTML Escape는 원래 데이터를 없애는 것이 아니라 HTML에서 안전하게 표현할 수 있는 형태로 변환합니다.
10 < 20
↓
10 < 20
브라우저에서는 다시 사람이 읽을 수 있는 < 문자로 표시됩니다.
따라서 데이터를 훼손하지 않으면서 HTML 문맥에서 안전하게 표현하는 것이 Escape의 중요한 특징입니다.
JavaScript에서는 innerHTML을 조심해야 한다
요즘 웹 개발에서는 서버에서 HTML을 만드는 것뿐만 아니라 JavaScript로 DOM을 변경하는 경우가 많습니다.
특히 다음과 같은 코드가 있다면 한 번 더 살펴볼 필요가 있습니다.
const userInput = getUserInput();
document.querySelector("#result").innerHTML = userInput;
innerHTML은 전달된 문자열을 HTML로 해석할 수 있기 때문입니다.
단순히 사용자 입력을 텍스트로 보여주려는 목적이라면 상황에 따라 textContent를 사용하는 것이 더 적절합니다.
const userInput = getUserInput();
document.querySelector("#result").textContent = userInput;
두 코드는 비슷해 보이지만 데이터를 처리하는 방식이 다릅니다.
textContent는 문자열을 텍스트로 다루는 데 적합하기 때문에 HTML을 의도하지 않은 사용자 입력을 화면에 표시할 때 유용합니다.
프레임워크를 사용하면 자동으로 처리해줄까?
React, Vue, Angular 같은 프론트엔드 프레임워크를 사용하는 경우 일반적인 데이터 바인딩에서는 HTML을 직접 삽입하는 것보다 안전한 방식으로 처리되는 경우가 많습니다.
하지만 그렇다고 해서 XSS가 자동으로 완전히 사라지는 것은 아닙니다.
예를 들어 HTML을 직접 삽입하는 기능을 사용하거나 프레임워크의 특정 API를 이용해 HTML을 렌더링하면 별도의 주의가 필요합니다.
특히 개발자가 “HTML을 그대로 보여줘야 한다”는 이유로 HTML 문자열을 직접 삽입하는 기능을 사용한다면 해당 데이터가 어디에서 왔는지 반드시 확인해야 합니다.
결국 중요한 것은 프레임워크 이름이 아니라 신뢰할 수 없는 데이터가 HTML로 해석될 수 있는 위치에 들어가는지 여부입니다.
HTML Escape와 URL Encoding은 다르다
웹 개발을 하다 보면 Escape와 Encoding이라는 용어가 많이 등장합니다.
하지만 모두 같은 의미로 사용되는 것은 아닙니다.
HTML 문맥에서 사용하는 HTML Escape와 URL에서 사용하는 URL Encoding은 목적이 다릅니다.
예를 들어 URL의 Query Parameter를 처리할 때는 URL Encoding이 필요할 수 있습니다.
반면 HTML 화면에 사용자 입력값을 출력할 때는 HTML 문맥에 맞는 Escape 또는 Encoding이 필요합니다.
즉,
HTML → HTML Escape
URL → URL Encoding
JavaScript → JavaScript 문맥에 맞는 처리
SQL → Parameter Binding
처럼 데이터가 들어가는 문맥에 맞는 방어 방법을 선택해야 합니다.
이 부분은 웹 보안을 공부할 때 상당히 중요한 개념입니다.
라이브러리를 사용하는 것이 좋다
HTML Escape를 직접 구현하려고 다음과 같은 코드를 만드는 경우가 있습니다.
value.replace("<", "<")
.replace(">", ">");
간단한 테스트에서는 가능해 보이지만 실제 서비스에서는 이런 식으로 보안 처리를 직접 구현하는 것은 권장하기 어렵습니다.
HTML에는 다양한 문법과 문맥이 존재하고, 단순히 몇 가지 문자만 바꾼다고 모든 상황을 처리할 수 있는 것이 아니기 때문입니다.
따라서 사용하는 언어나 프레임워크에서 제공하는 검증된 Escape/Encoding 라이브러리나 템플릿 엔진의 자동 escaping 기능을 활용하는 것이 좋습니다.
개발할 때 어떤 부분을 확인해야 할까?
기존 프로젝트의 XSS 가능성을 점검한다면 사용자 입력이 화면에 출력되는 부분부터 찾아보는 것이 좋습니다.
예를 들면 다음과 같습니다.
게시판 제목
댓글
검색어
회원 이름
오류 메시지
URL 파라미터
관리자 페이지
프론트엔드 코드에서는 특히 다음과 같은 패턴을 찾아볼 수 있습니다.
element.innerHTML = userInput;
또는 HTML 문자열을 직접 만들어 DOM에 삽입하는 코드입니다.
서버 코드에서도 다음과 같이 사용자 입력을 HTML 문자열에 직접 붙이는 부분이 있는지 확인해야 합니다.
html += "<div>" + userInput + "</div>";
이런 부분을 발견했다면 단순히 해당 문자열에 특정 문자를 삭제하는 것보다 어떤 HTML 문맥에 데이터가 들어가는지 확인하고 적절한 출력 인코딩을 적용하는 것이 중요합니다.
HTML Escape를 한 문장으로 정리하면
HTML Escape를 어렵게 생각할 필요는 없습니다.
한 문장으로 정리하면 다음과 같습니다.
“HTML에서 코드로 해석될 수 있는 문자를 안전한 문자열 형태로 변환해서 출력하는 것.”
특히 사용자 입력값을 HTML 화면에 출력할 때 중요한 개념입니다.
다만 모든 XSS를 HTML Escape 하나만으로 해결할 수 있다고 생각해서는 안 됩니다.
출력 문맥에 따라 적절한 인코딩을 적용하고, HTML을 직접 삽입하는 기능을 주의해서 사용하며, 필요한 경우 CSP 같은 추가적인 보안 정책도 함께 고려해야 합니다.
마무리
HTML Escape는 처음 보면 단순히 <를 <로 바꾸는 기술처럼 보입니다.
하지만 실제 의미는 그보다 중요합니다.
웹 애플리케이션은 사용자 입력을 계속 받아들이고, 그 데이터를 다시 화면에 보여주는 작업을 반복합니다.
이 과정에서 데이터와 HTML 코드의 경계를 명확하게 유지하는 것이 중요합니다.
사용자가 입력한 값이 단순한 문자열이라면 브라우저에서도 문자열로 처리되어야 합니다.
그런데 이것이 HTML 코드로 해석될 수 있는 순간 XSS라는 보안 문제가 발생할 수 있습니다.
그래서 개발할 때는 다음 원칙을 기억해두면 좋습니다.
사용자 입력은 신뢰하지 않는다.
↓
HTML에 출력할 때는 적절하게 Escape한다.
↓
가능하면 텍스트 전용 API를 사용한다.
↓
문맥에 맞는 Encoding을 적용한다.
특히 innerHTML이나 사용자 입력을 HTML 문자열에 직접 연결하는 코드를 발견했다면 그냥 지나치지 않는 것이 좋습니다.
웹 보안은 공격 코드를 외우는 것보다, 데이터가 어떤 방식으로 해석되는지를 이해하는 것에서 시작합니다.
HTML Escape도 결국 이 원칙을 이해하기 위한 가장 기본적인 개념 중 하나입니다.
홍TV



![Eclipse에서 갑자기 "[m2e] Lifecycle Mapping" 오류? Maven 프로젝트가 빨간 줄 뜨는 이유 4 Eclipse에서 갑자기 [m2e] Lifecycle Mapping 오류? Maven 프로젝트가 빨간 줄 뜨는 진짜 이유](https://hongtv.co.kr/wp-content/uploads/2026/09/62edeb0f-aaf8-42cc-af31-1a91e54bc187-300x200.png)
댓글 0
첫 댓글을 남겨보세요.