회사에서 공지사항 게시판을 만드는데 html태그까지 들어있는 전체를 string으로 만든 후 이를 가져와서 보여줘야 하는 일이 있었다. 이때 dangerouslySetInnerHTML 을 사용했다.
innerHTML, dangerouslySetInnerHTML 은 string을 파싱해서 HTML로 만들어준다.
(react 의 dangerouslySetInnerHTML은 결과적으로는 javascript 의 innerHTML과 똑같다.)
그리고 이는 xss (cross site script) 공격을 당할 수 있다.
xss란? (Cross Site Script)
XSS 공격은 공격자에 의해 작성된 스크립트가 다른 사용자에게 전달되는 기법이다. 다른 사용자의 웹 브라우저에서 적절한 검증 없이 실행되기 때문에 사용자의 세션을 탈취하거나 , 웹 사이트 변조 또는 악의적인 사이트로 사용자를 이동시킬 수 있다.
xss 는 OWASP(Open Web Application Security Project) top 10 2021년도 중 3위이다.
꽤나 순위가 높은 빈번한 공격이다.
xss도 이러한 injection 공격들 중 하나이다. malicious 한 script를 inject해서 사이트를 교란시키는 것이기 때문.
그래서 innerHTML에 script 태그가 있으면 이는 실행되지 않도록 자체적으로 막혀있다.
(출처: https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0)
여기서 드는 생각.
그럼 안전한 거 아닌가? 어차피 script가 실행 안되면?
하지만 여전히 위험요소는 있다.
const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alert
이런식으로 img 태그의 onError 에 넣어서 script 를 실행시키는 우회적인 방법이 있다.
따라서 script 태그가 실행되지 않는다고 방심하면 안된다.
그렇다면 어떻게 해야 안전하게 html 태그가 있는 string 을 파싱할 수 있을까?
바로 code를 sanitising 하는 것이다.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">
code sanitizing은 말 그대로 코드를 미리 선 파싱 해서 위험요소들을 모두 제거한 후 렌더링하는 것이다.
sanitize 를 하면 이런식으로 위험요소를 잘라서 리턴해주기 때문에 신뢰할 수 없는(untrusted) string이 와도 안심하고 쓸 수 있다.
code를 sanitizing 하기 위해서는
1. DOM Purify
2. HTML Sanitizer API
를 사용하는 방법이 있다.
1번은 외부 플러그인이고
2번은 브라우저에서 자체적으로 제공하는 방법이라고 한다.
다만 sanitizer api는 experimental feature기 때문에 아직 모든 브라우저들에 적용되지 않는다. 내 웹사이트가 지원되는지 확인하자.
결론!
DOM 에 데이터를 직접 넣는 것(DOM Injection)은 매우 위험하다!! 어떤 string이 올지 신뢰할 수 없기 때문! (untrusting data)
이 경우 Sanitize 라이브러리로 데이터를 한 번 정리한 후 렌더해야 한다!
그냥...DOM injection을 하지말자....모든 편리한 것에는 댓가가 따른다....
innerHTML도 이렇게 위험한데, eval 함수는 script 태그를 실행하니 당연히 훠얼씬 위험하겠죠?
괜히 mdn 문서에서 절대 쓰지 말라고 경고멘트를 날리는게 아니다!
쓰지 말라면 쓰지 말자.
참고문서:
https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=is_king&logNo=221602193148
실제로 xss 해킹 예제를 보여주는 블로그다. 매우 흥미롭게 읽었다.