올해 프로젝트가 거의 막바지에 왔다.
그리고 막바지에 오면 이제 엄청 테스트를 많이 하게 되는데,
깜빡하고 XSS 테스트를 많이 안 해봤다.
다행히 이번 프로젝트의 요구사항에서 CRUD 기능이 딱 하나고
해당 기능만 조심해서 봤다.
그런데 왠걸, 바로 XSS 가 터졌다.
이유는 아래와 같은 상황 때문이었다.
참고로 백엔드 코드를 Spring 으로 짜고 있는 상태다. 그리고 WAS는 톰캣이다.
{"content": "<script>alert('XSS ATTACK')</script>"}
"content"
내용을 받아서 DB 에 INSERT 를 한다.jquery.html('html 문자열')
을 통해서 화면에 뿌린다.<script>alert('XSS ATTACK')</script>
이 실행된다 ==>
XSS 발생 😨jquery 에는 html 조작과 관련한 다양한 유틸리티 메소드를 제공한다.
하지만 이런 메소드들의 대부분이 XSS 에 대해서 안전하지 않다.
특히 자주 쓰는 jquery.html()
, jquery.append*()
메소드 모두 XSS 에 안전하지 않다.
단적으로 아래와 같은 codepen 테스트만 돌려봐도 알 수 있다.
해결법은 결국 화면단에서 XSS 공격이 가능한 문자열을 브라우저가 일반적인 TAG 처럼
다룬지 않게만 하면된다.
그렇다면 브라우저에서 이런 XSS 공격성 문자열이 실행되지 않으면서도
화면에 표출시키려면 어떡할까?
나는 아래 3가지 방법을 알아내고, 그 중에서 마지막 방법을 사용했다.
(() => {
const xssAttackString = '<script>alert("XSS ATTACK!!!!!")</script>';
// 방법1: innerText 사용
let docFrag = document.createDocumentFragment();
let spanElem = document.createElement("span");
spanElem.innerText = xssAttackString; // 완전히 문자열로 받아들인다.
docFrag.appendChild(spanElem);
document.body.appendChild(docFrag);
// 몇몇 분들은 innerHTML 로 테스트해도 xssAttackString 가 먹히지 않는 것을
// 확인했을 것이다. 이건 HTML 명세에 이미 정해진 동작 방식이다.
// 하지만 xss 공격은 script 만 쓸 수 있는 게 아니다.
// innerHTML 의 경우 <img src="/bad/code"/> 같은 경우에도 실행이 되버린다.
// 그리고 뭐니뭐니 해도 화면에 해당 내용이 보이지 않는다는 문제도 있다.
// 방법2: jQuery 의 .text() 사용
// let spanElem2 = document.createElement("span");
// $(spanElem2).text(xssAttackString);
// $(body).append(spanElem2);
// 방법3: 들어온 HTML 문자열을 html entity 로 변환 시켜주는 메소드를 생성한다.
// 나의 경우에는 기본으로 제공되는 document.createTextNode 를 사용해서 이를 해결했다.
// 이 방법은 특히 jquery 의 html(), append*() 메소드와 함께 쓸 때 사용하면 편하다.
// html, append 하기 직전에 이 메소드를 한번 수행해주기만 하면 된다.
function changeToHtmlEntity(str) {
//이렇게 하면 자동으로 html Entity 변환해준다.
let txt = document.createTextNode(str);
let p = document.createElement('p');
p.appendChild(txt);
return p.innerHTML;
}
let docFrag3 = document.createDocumentFragment();
let spanElem3 = document.createElement("span");
// <script>alert("XSS ATTACK!!!!!")</script> 로 변환!
spanElem3.innerHTML = changeToHtmlEntity(xssAttackString);
docFrag3.appendChild(spanElem3);
document.body.append(docFrag3);
// $(body).append(spanElem3); // 이래도 큰 문제가 없습니다!
})();
https://developer.mozilla.org/ko/docs/Web/API/Document/createDocumentFragment
https://stackoverflow.com/questions/8318581/html-vs-innerhtml-jquery-javascript-xss-attacks
https://stackoverflow.com/questions/994331/how-to-unescape-html-character-entities-in-java
다양한 XSS 공격 샘플: http://inxight.blogspot.com/2009/08/xss-%EA%B3%B5%EA%B2%A9-sample-code.html