XSS 방어 (Preventing XSS)
자바스크립트를 다룰 때 XSS(Cross-Site Scripting)는 피할 수 없는 보안 문제 중 하나다. 공격자가 악성 스크립트를 삽입해서 사용자의 브라우저에서 실행되게 만드는 이 공격을 막으려면 철저한 방어가 필요하다. 이번에는 XSS의 기본 개념부터 방어 방법까지 코드와 함께 하나씩 풀어보려고 한다.
XSS를 잘 이해하고 막아내면 웹 애플리케이션의 안전성이 한층 높아진다. 단계별로 차근차근 알아보자.
XSS란 무엇인가
XSS는 사용자가 입력한 데이터를 제대로 처리하지 않아 악성 코드가 웹 페이지에 삽입되는 공격이다. 예를 들어, 사용자가 입력란에 <script>alert('해킹!')</script>
를 넣으면 그 코드가 그대로 실행될 수 있다.
간단한 예를 들어보자:
const userInput = "<script>alert('해킹!')</script>";
document.body.innerHTML = userInput;
// 경고창 "해킹!"이 뜬다
이렇게 innerHTML
로 바로 넣으면 스크립트가 실행된다. XSS는 이런 식으로 시작된다.
1. 기본 방어: HTML 이스케이프
가장 기본적인 방법은 사용자가 입력한 데이터를 HTML 엔티티로 변환하는 것이다. 그러면 스크립트 태그가 실행되지 않고 텍스트로 표시된다:
function escapeHTML(str) {
const htmlEntities = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return str.replace(/[&<>"']/g, match => htmlEntities[match]);
}
const userInput = "<script>alert('해킹!')</script>";
const safeInput = escapeHTML(userInput);
document.body.innerHTML = safeInput;
// "<script>alert('해킹!')</script>"로 표시됨
<
와 >
가 각각 <
와 >
로 바뀌면서 코드가 실행되지 않았다.
2. innerHTML 대신 textContent 사용
innerHTML
은 HTML로 해석되지만, textContent
는 순수 텍스트로 처리한다:
const userInput = "<script>alert('해킹!')</script>";
document.body.textContent = userInput;
// "<script>alert('해킹!')</script>"로 표시됨
textContent
를 사용하면 태그가 해석되지 않고 그대로 텍스트로 출력된다.
3. DOMPurify로 강력한 방어
직접 이스케이프 함수를 만드는 것도 좋지만, DOMPurify 같은 라이브러리를 활용하면 더 안전하다:
// DOMPurify 라이브러리 추가 후
const userInput = "<script>alert('해킹!')</script><p>안녕</p>";
const clean = DOMPurify.sanitize(userInput);
document.body.innerHTML = clean;
// "<p>안녕</p>"만 남고 스크립트는 제거됨
DOMPurify는 위험한 태그를 제거하면서 허용된 HTML은 유지한다.
4. 이벤트 핸들러에서의 XSS
사용자 입력이 이벤트 핸들러에 들어갈 때도 주의가 필요하다:
const userInput = "alert('해킹!')";
document.body.innerHTML = `<button onclick="${userInput}">클릭</button>`;
// 버튼 클릭 시 "해킹!" 경고창 실행
이를 방어하려면 이벤트 핸들러를 직접 추가해야 한다:
const button = document.createElement('button');
button.textContent = '클릭';
button.addEventListener('click', () => {
console.log('안전한 클릭');
});
document.body.appendChild(button);
이렇게 하면 사용자 입력이 이벤트 속성에 직접 들어가지 않는다.
5. JSON 데이터 처리
서버에서 받은 JSON 데이터도 XSS 위험이 있을 수 있다:
const unsafeJSON = {
content: "<script>alert('해킹!')</script>"
};
document.body.innerHTML = unsafeJSON.content;
// 경고창 실행
이를 막으려면 JSON 데이터를 정화해야 한다:
const unsafeJSON = {
content: "<script>alert('해킹!')</script>"
};
const safeContent = escapeHTML(unsafeJSON.content);
document.body.innerHTML = safeContent;
// 텍스트로 표시됨
JSON 데이터를 화면에 뿌리기 전에 항상 확인하자.
6. URL 파라미터와 XSS
URL 쿼리에서 받은 데이터도 위험할 수 있다:
const params = new URLSearchParams(window.location.search);
const input = params.get('data'); // ?data=<script>alert('해킹!')</script>
document.body.innerHTML = input;
// 경고창 실행
URL 데이터도 이스케이프 처리해야 한다:
const params = new URLSearchParams(window.location.search);
const input = params.get('data');
const safeInput = escapeHTML(input);
document.body.innerHTML = safeInput;
쿼리 파라미터도 안전하게 다루는 습관을 들이자.
7. CSP(Content Security Policy) 활용
CSP는 브라우저 수준에서 XSS를 막아준다. 헤더에 정책을 추가하면 된다:
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
이렇게 하면 외부 스크립트가 로드되지 않는다:
document.body.innerHTML = "<script src='https://evil.com/hack.js'></script>";
// CSP에 의해 차단됨
CSP는 추가적인 방어선 역할을 한다.
8. 심화: 속성 값에서의 XSS
HTML 속성에 삽입된 스크립트도 위험하다:
const userInput = "\" onclick=\"alert('해킹!')";
document.body.innerHTML = `<input value=${userInput}>`;
// 클릭 시 경고창 실행
이를 막으려면 속성을 동적으로 설정할 때도 안전하게 처리해야 한다:
const input = document.createElement('input');
input.setAttribute('value', escapeHTML("입력값"));
document.body.appendChild(input);
속성 값도 항상 확인하고 설정하자.
9. 프레임워크에서의 XSS 방어
React 같은 프레임워크는 기본적으로 XSS를 방어한다:
function Comment(props) {
const userInput = "<script>alert('해킹!')</script>";
return <div>{userInput}</div>;
}
// "<script>alert('해킹!')</script>"로 표시됨
하지만 dangerouslySetInnerHTML
을 사용할 때는 주의해야 한다:
function DangerousComponent() {
const userInput = "<script>alert('해킹!')</script>";
return <div dangerouslySetInnerHTML={{__html: userInput}} />;
}
// 경고창 실행
프레임워크를 믿더라도 위험한 기능은 피하자.
10. 종합적인 방어 전략
XSS를 완벽히 막으려면 여러 방법을 조합해야 한다:
function renderSafeContent(userInput) {
const escaped = escapeHTML(userInput);
const cleaned = DOMPurify.sanitize(escaped);
const div = document.createElement('div');
div.textContent = cleaned;
document.body.appendChild(div);
}
renderSafeContent("<script>alert('해킹!')</script><p>안녕</p>");
// 안전하게 텍스트로 표시됨
이스케이프, 정화, DOM 조작을 모두 활용하면 더 튼튼한 방어가 된다.
마무리
XSS는 자바스크립트 개발에서 늘 마주치는 위협이다. HTML 이스케이프부터 CSP, 프레임워크 활용까지 다양한 방법으로 방어할 수 있다. 사용자가 입력하는 모든 데이터를 의심하고, 여러 층의 보호를 적용하면 안전한 웹을 만들 수 있다.
'코딩 공부 > 자바스크립트' 카테고리의 다른 글
89. 자바스크립트 HTTPS와 보안 헤더 (HTTPS and Security Headers) (1) | 2025.03.30 |
---|---|
88. 자바스크립트 CSRF 방어 (Preventing CSRF) (0) | 2025.03.29 |
86. 자바스크립트 렌더링 최적화 (Rendering Optimization) (2) | 2025.03.29 |
85. 자바스크립트 메모리 관리 (Memory Management) (1) | 2025.03.28 |
84. 자바스크립트 지연 로딩과 비동기 로딩 (Lazy Loading and Async Loading) (1) | 2025.03.28 |