문제
https://dreamhack.io/wargame/challenges/269
문제코드
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
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**]"
users = {
'guest': 'guest',
'admin': FLAG
}
session_storage = {}
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():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
@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", "")
session_id = os.urandom(16).hex()
session_storage[session_id] = 'admin'
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
session_id = os.urandom(8).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
users[username] = pw
return 'Done'
app.run(host="0.0.0.0", port=8000)
코드 분석
@app.route("/")
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
메인 페이지 /로 들어가게 되면, 쿠키로부터 session_id를 받고 세션아이디로 유저 정보를 가져온다.
admin으로 접속되면 FLAG가 나오는 구조이다.
users = {
'guest': 'guest',
'admin': FLAG
}
유저 정보는 guest와 admin 두 개가 있는 것으로 파악된다.
@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
/vuln 에서는 저번 csrf-1 과 같이 xss 공격이 성공하지 못하도록 저 단어들은 *로 치환이 되게 되어있다.
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
elif request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
try:
pw = users[username]
except:
return '<script>alert("not found user");history.go(-1);</script>'
if pw == password:
resp = make_response(redirect(url_for('index')) )
session_id = os.urandom(8).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
/login으로 들어오게 되면, GET요청일때는 그냥 login.html 템플릿이 보이게 되어있다.
POST요청이라면....
username과 password 를 폼에서 받고, users에서 pw 정보를 가져온다.
만약에 일치하게 된다면, session_id 를 생성하여 session_storage 에 해당 유저의 정보를 저장한다. 그리고 쿠키도 저장하는 것 같다.
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
users[username] = pw
return 'Done'
app.run(host="0.0.0.0", port=8000)
/change_password 주소창 이름만 보면 비밀번호를 바꾸는 곳인 거 같은데, pw와 세션id를 받고나서, 세션 저장소에서 username을 받아오고 비번을 바꿔오는 것 같다.
@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", "")
session_id = os.urandom(16).hex()
session_storage[session_id] = 'admin'
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
이 flag 부분이 제일 중요하다고 생각되는 이유는,
session_id 를 생성을 하고 admin으로 저장하는 것이 보인다.
즉 admin 세션이 생성이 된 건데 저것을 활용하면 뭔가 할 수 있지 않을까이다.
풀이 전략
CSRF 라는 것은 일반 유저가 로그인 되어서 쿠키를 가지고 있는 상태에서 악의적인 사이트에 접속되었다가 거기서 무엇인가 악성 스크립트로 인하여 의도하지않은 방식을 실행하는 것이다. 일반 유저로 접속해서...flag 부분을 통해 비밀번호를 뭔가 바꾸는 식으로 하면 되지 않을까? 딱 이 생각만 가지고 문제를 풀어보려 했다.
처음 홈페이지에 접속하면 이런 화면이 뜬다.
guest/guest 로 로그인을 해준 메인 화면이 위와 같고
이게 guest의 session_id 인 것 같다.
flag 에 진입해서
<img src="/change_password?pw=1"/> 을 입력 후 제출을 누르면
@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", "")
session_id = os.urandom(16).hex()
session_storage[session_id] = 'admin'
if not check_csrf(param, {"name":"sessionid", "value": session_id}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
POST 요청으로 param 부분에서 <img src="/change_password?pw=1"/>이 저장이 되고
session_id 는 새롭게 생성이 되어서 그게 admin의 세션이 저장이 된다.
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)
check_csrf로 넘어가졌을 때, param 부분이 문자열로 인코잉디 되고 이게 url에 저장이 된다.
@app.route("/change_password")
def change_password():
pw = request.args.get("pw", "")
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
users[username] = pw
return 'Done'
app.run(host="0.0.0.0", port=8000)
img 태그에다가
<img src="/change_password?pw=1">로 설정을 했기에, pw =1로 저장이 되어 있고, session_id는 현재 admin 세션이 저장되어 있기에,
admin 의 비밀번호는 1로 변경이 완료되었다.
그래서 admin/1 로 접속을 시도하면
플래그가 나왔다.
플래그는
DH{c57d0dc12bb9ff023faf9a0e2b49e470a77271ef}
이다.
에필로그
딱히 큰 힌트 없이 풀었던 문제 이다. 이해하기는 좀 힘들긴 했는데 그래도 재밌게 푼 문제 같다.
'보안 스터디 > 웹 해킹' 카테고리의 다른 글
[드림핵/웹해킹] ServerSide: SQL Injection (0) | 2024.01.07 |
---|---|
[드림핵/웹해킹] Background: Relational DBMS (0) | 2024.01.07 |
[드림핵/워게임] CSRF-1 (웹해킹) (1) | 2024.01.07 |
[드림핵/웹해킹] ClientSide: CSRF (1) | 2024.01.06 |
[드림핵/워게임] xss-2 (웹 해킹) (1) | 2024.01.05 |