https://dreamhack.io/wargame/challenges/26
여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다.
CSRF 취약점을 이용해 플래그를 획득하세요.
희생자가 본인의 의사와 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 것으로, 해당 취약점을 활용하여 공격자는 사용자에게 비밀번호 변경, 계정 삭제, 로그아웃 등 여러 행위를 유도 할 수 있다.
#!/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()
print(str(e))
# return str(e)
return False
driver.quit()
return True
def check_csrf(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():
param = request.args.get("param", "").lower()
xss_filter = ["frame", "script", "on"]
for _ in xss_filter:
param = param.replace(_, "*")
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_csrf(param):
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", None)
if text:
memo_text += text
return render_template("memo.html", memo=memo_text)
@app.route("/admin/notice_flag")
def admin_notice_flag():
global memo_text
if request.remote_addr != "127.0.0.1":
return "Access Denied"
if request.args.get("userid", "") != "admin":
return "Access Denied 2"
memo_text += f"[Notice] flag is {FLAG}\n"
return "Ok"
app.run(host="0.0.0.0", port=8000)
/vuln 라우터에서 "script", "on", "frame" 등의 키워드를 읽을 시에 _"*" 로 대체 되는 것을 확인할 수 있다.
따라서 xss 공격은 어려울 듯 하다.
@app.route("/vuln")
def vuln():
param = request.args.get("param", "").lower()
xss_filter = ["frame", "script", "on"]
for _ in xss_filter:
param = param.replace(_, "*")
return param
다음으로 notice_flag 라우터는 접속 요청 사용자가 로컬호스트거나, userid 의 파라미터값이 admin 이 아닐 때, Access Denied 문자열을 출력한다.
@app.route("/admin/notice_flag")
def admin_notice_flag():
global memo_text
if request.remote_addr != "127.0.0.1":
return "Access Denied"
if request.args.get("userid", "") != "admin":
return "Access Denied 2"
memo_text += f"[Notice] flag is {FLAG}\n"
return "Ok"
따라서 로컬호스트가 아니면서 userid 가 admin 일때만 memo 에 flag 를 작성한다.
이어서 /memo 페이지를 살펴보면 아래와 같다.
@app.route("/memo")
def memo():
global memo_text
text = request.args.get("memo", None)
if text:
memo_text += text
return render_template("memo.html", memo=memo_text)
memo_text 가 global 로 선언되어 notice_flag 에서의 memo_text 의 값을 반환하는 것을 알 수 있다.
vuln(csrf) page 에 접속하면, alert(1) 을 실행되는 script 구문을 전달한다.
응답화면에는 script 구문이 필터링 되어있다. 앞서 소스코드에서 확인했듯, script 문자열이 <*> 로 대체되었다.
userid 가 이면서 IP 가 호스트 일시 /admin/notice_flag 에 접속하도록해야한다.
on 문자도 필터링 되기 때문에 img 태그를 사용하여 아래와같이 공격구문을 구성했다.
이는 /vuln 페이지를 이용하여 서버가 직접 /admin/notice_flag 를 방문하게 하는 스크립트다.
<img src="/admin/notice_flag?userid=admin">
이를 /flag 페이지에 입력한 후, memo 페이지를 방문하면 플래그를 확인할 수 있다.