Iriton's log
[Dreamhack] Lv.3 XSS Filtering Bypass Advanced 본문
https://dreamhack.io/wargame/challenges/434
코드 분석
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open("./flag.txt", "r").read()
except:
FLAG = "[**FLAG**]"
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
try:
service = Service(executable_path="/chromedriver")
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
driver = webdriver.Chrome(service=service, options=options)
driver.implicitly_wait(3)
driver.set_page_load_timeout(3)
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(url)
except Exception as e:
driver.quit()
# return str(e)
return False
driver.quit()
return True
def check_xss(param, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie)
def xss_filter(text):
_filter = ["script", "on", "javascript"]
for f in _filter:
if f in text.lower():
return "filtered!!!"
advanced_filter = ["window", "self", "this", "document", "location", "(", ")", "&#"]
for f in advanced_filter:
if f in text.lower():
return "filtered!!!"
return text
@app.route("/")
def index():
return render_template("index.html")
@app.route("/vuln")
def vuln():
param = request.args.get("param", "")
param = xss_filter(param)
return param
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html")
elif request.method == "POST":
param = request.form.get("param")
if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
memo_text = ""
@app.route("/memo")
def memo():
global memo_text
text = request.args.get("memo", "")
memo_text += text + "\n"
return render_template("memo.html", memo=memo_text)
app.run(host="0.0.0.0", port=8000)
/vuln 페이지
request.args에 args["param"]라는 값이 있다면 그 값을 반환할 것이고 아니라면 ""을 대신 반환한다.
param 값으로 xss_filter 함수를 호출한다.
xss_filter()
xss_filter 함수에서는 script, on, javascript, window, self, this, document, location, (, ), &# 문자열을 모두 필터링 한다.
전달 받은 파라미터를 소문자로 변경해서 필터링 하는 거라 대소문자 변경도 의미가 없을 것이고
필터링 후 공백으로 치환하는 게 아니라 filtered!!!라는 문자열로 치환되는 것이라 scrscriptipt 와 같이 의도한 문자열 사이에 필터링될 문자열을 넣어서 공백으로 치환됐을 시 의도한 문자열이 완성되는 그런 우회 방식도 먹히지 않을 것이다.
일단 우회는 코드 분석 후 해 보기로 하고 넘어가자.
/flag 페이지
GET 메서드면 flag.html을 띄워주고,
POST 메서드면 param 라는 파라미터 값을 받아 param이라는 변수에 저장한다.
그리고 check_xss라는 함수에 param과 name이 flag이고 value가 FLAG.strip()인 값을 전달한다.
name이 flag이고, value가 FLAG값인 쿠키를 check_xss라는 함수에 전달하는 것이다.
그래서 check_xss 함수에 보내는 쿠키의 value 값이 우리가 찾아야 할 flag 값으로 추측된다.
check_xss()
param 값을 가지고 /vuln 페이지에 접근하여 read_url에 넘긴다.
이때 /vuln에서는 xss 필터링 함수를 거치기 때문에 param 값이 필터링에 걸리지 않아야 flag를 띄울 수 있게 될 것이다.
read_url()
로컬 서버에 접근해서
쿠키를 추가하는데 이때 쿠키는 flag:FLAG 가 된다.
그리고 url에 get 요청을 한다.
즉, 서버에 파라미터가 flag 값으로 지정된다.
url이 무엇이냐가 중요한데, url에서 쿠키 값을 우리가 확인할 수 있는 어딘가로 넘겨 주면 된다.
read_url()
/memo 에서 파라미터 값을 출력해 주니까
/flag에서 파라미터 값에 스크립트를 실행시켜 /memo 페이지에 쿠키를 추가해 보내주면 memo 페이지에서 flag 값을 볼 수 있을 것이다.
Exploit
xss 필터링을 우회 하는 게 관건인데...
일반적인 XSS 공격에서 흔히 사용되는 onerror, onclick 등의 이벤트 핸들러는 필터링 규칙에 의해 차단된다.
src 속성을 이용하는 것 중 그 자체로 필터링이 안 되는 건 iframe이 있다.
드림핵 XSS Filtering Bypass - 1 강의에서 URL 정규화 라는 힌트를 얻었는데,
URL 정규화란 동일한 리소스를 나타내는 서로 다른 URL을 통일된 형태로 변환하는 과정이다.
이 과정에서 특수 문자가 제거되며, 대소문자가 통일된다.
예를 들어 %09 (tab), %0A (new line), %0D (carriage return) 같은 문자가 제거된다.
<iframe src="javascri%09pt:alert'1'"/>
위에서 %09는 tab 공백 문자를 의미하며 필터링 함수를 거칠 땐 그대로 인식되어 script가 필터링되지 않지만, URL 정규화로 인해 공백으로 치환된다. 따라서 위처럼 alert'1' 기능을 할 수 있던 것이다.
이를 이용해서 사용자 쿠키 정보를 가져와서 memo 파라미터에 전달하는 코드를 작성하면 된다.
근데 한 가지 더 고려할 점은 GET 메서드는 쿼리 문자열을 통해 데이터를 전송하기 때문에 이때 URL 정규화 과정을 거치며 변환되는 것인데
POST 메서드는 리퀘스트 body에 데이터를 포함하여 전송한다. 따라서 필터링 로직이 쿼리 파라미터가 아닌 요청 본문을 검사한다. 즉, URL 쿼리 문자열이 아니라 폼 데이터나 JSON 형식으로 전송되므로 URL 정규화가 이뤄지지 않아서 실제 탭으로 입력을 해야 될 것이다.
<iframe src="javascri pt:locatio n.href='/memo?memo='%2bdocumen t.cookie">
/flag 페이지에서 POST 메서드로 위의 파라미터 전달하면 플래그가 뜬다
'WebHacking > WarGame' 카테고리의 다른 글
[Dreamhack] Lv.3 chocoshop (3) | 2024.11.08 |
---|---|
[Dreamhack] Lv.3 CSP Bypass Advanced (3) | 2024.11.06 |
[Dreamhack] Lv.2 blind-command (1) | 2024.09.30 |
[Dreamhack] Lv.2 web-ssrf (0) | 2024.09.25 |
[webhacking.kr] old-34 (0) | 2024.05.29 |