[DOMPurify] XSS 공격 방지 :: 마이구미
이 글은 XSS 공격 방지에 도움을 줄 수 있는 라이브러리 dompurify을 다룬다.
바닐라 자바스크립트가 아닌 현재 개발 환경에서는 XSS 를 사실 대부분 신경을 크게 쓰지 않을 것이다.
흥미와 이해를 위해 React 를 조금 곁들였다.
조금이나마 도움을 줄 수 있는 라이브러리들을 소개하는 카테고리로 분류된 글이다.
알아두면 좋은 라이브러리
XSS 공격은 간단하게 말하면, 사용자 브라우저에서 예상치 못한 자바스크립트 코드를 실행하여 악의적인 행동을 취하는 것이다.
대표적으로 사용자 브라우저에 존재하는 쿠키, 세션, 로컬스토리지 등의 값을 긁어가는 것이다.
div.innerHTML = '<script deferred>alert("XSS Attack");</script>';
div.innerHTML = '<img src="x" onerror="alert("XSS Attack")">';
바닐라 자바스크립트를 사용한다면, DOM 에 주입하기 위해 innerHTML 를 사용한다.
예를 들어, 외부 라이브러리에서 받아온 HTML 코드를 DOM 에 innerHTML 를 통해 주입한다면?
이러한 경우들은 XSS 공격 위험에 노출될 수 있는 것이다.
우리는 자유롭게 브라우저가 실행되고 있는 순간에도 문자열 형태를 DOM 에 새로운 요소나 속성을 주입할 수 있다.
단순 문자열 형태가 그대로 DOM 에 주입할 수 있다는 것을 활용하여 많은 방식으로 XSS 공격을 시도할 수 있는 것이다.
그렇다면, 대안은 무엇인가?
위 예제들은 HTML 코드이지만, 실제로는 문자열이다.
문자열이 자동으로 HTML 요소로 인식될 수 있는 것은 innerHTML 의 기능이다.
그렇다. innerHTML 을 사용하지 않으면 된다.
div.textContent = '<img src="x" onerror="alert("XSS Attack")">';
innerHTML 대신 textContent 를 사용하는 것이 가장 쉬운 대안이다.
HTML 요소로 랜더하는 것이 아닌 선언된 문자열을 그대로 출력하는 것이다.
비슷한 사례를 React 를 통해 확인해보자.
const App = () => {
const html = '<img src="x" onerror="alert("XSS Attack")">';
return (
<div>
{ html }
</div>
)
}
위 예제의 출력 결과는 어떻게 될까?
img 태그가 노출되는 것이 아닌 문자열 그대로 출력될 것이다.
img 태그를 출력하고 싶다면 dangerouslySetInnerHTML 를 사용해야한다.
const App = () => {
const html = '<img src="x" onerror="alert("XSS Attack")">';
return (
<div dangerouslySetInnerHTML={html} />
)
}
리액트에서는 innerHTML 방식의 위험을 상기시켜주는 방향을 알 수 있다.
innerHTML 방식을 꼭 써야하는 경우가 있다면?
그러한 경우 문자열을 한번 필터링해주면 된다.
이러한 용어를 Sanitize 라고 많이 부른다.
const sanitizeHTML = function (str) {
const temp = document.createElement('div');
temp.textContent = str;
return temp.innerHTML;
};
// Renders <h1><img src=x onerror="alert('XSS Attack')"></h1>
div.innerHTML = '<h1>' + sanitizeHTML('<img src=x onerror="alert(\'XSS Attack\')">') + '</h1>';
위와 같은 필터 기능을 거치는 것이다.
이러한 기능을 도와주는 것이 dompurify 라이브러리이다.
DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">
DOMPurify.sanitize('<svg><g/onload=alert(2)//<p>'); // becomes <svg><g></g></svg>
DOMPurify.sanitize('<p>abc<iframe//src=jAva	script:alert(3)>def</p>'); // becomes <p>abc</p>
DOMPurify.sanitize('<math><mi//xlink:href="data:x,<script>alert(4)</script>">'); // becomes <math><mi></mi></math>
DOMPurify.sanitize('<TABLE><tr><td>HELLO</tr></TABL>'); // becomes <table><tbody><tr><td>HELLO</td></tr></tbody></table>
DOMPurify.sanitize('<UL><li><A HREF=//google.com>click</UL>'); // becomes <ul><li><a href="//google.com">click</a></li></ul>
XSS 공격에 위험이 될만한 것들을 제거하여 innerHTML 방식을 사용할 수 있게 도와준다.
우리가 모르는 많은 케이스를 대응하고 있다는 것을 알 수 있다.
필요하다면, 이러한 라이브러리의 도움을 얻는 것을 선호한다.
리액트쪽에서의 관련 내용을 조금 더 보고 싶다면 다음 내용도 읽어보면 좋다.
https://overreacted.io/why-do-react-elements-have-typeof-property/
간략하게 정리하면 다음과 같다.
리액트 요소에는 $$typeof 라는 속성이 존재하고, 값은 심볼 타입이다.
결론적으로 이 속성은 XSS 공격과 관련이 있다.
예를 들어, JSON 형태의 서버의 응답값에서 리액트를 컴포넌트 형태로 내려줄 경우 XSS 공격에 노출된다.
JSON 에는 심볼 타입을 정의할 수 없기 때문에 이를 방지할 수 있다.