[Dreamhack] DreamDocs

서승기·2026년 7월 31일

Dreamhack

목록 보기
5/5
post-thumbnail

Introducing DreamDocs: a platform for browsing docs!

flag를 내뱉는 조건을 아무리 살펴봐도 X-user 가 admin인 거랑 랜덤한 게시글 id 찾기 인 것 같아서 브루트포스 돌려보았더니 정답이였던 문제였다.

문제 파일 부터 빠르게 확인해보자

app.py는 다음과 같다.

from flask import Flask, render_template, request, jsonify, abort
import os
import random


app = Flask(__name__)
app.secret_key = os.urandom(32)

FLAG =  "flag"

flag_doc_id = random.randint(100, 999)

documents = {
    flag_doc_id: {
        'title': 'Confidential Report - Access Restricted',
        'content': f'This is a confidential internal document.\n\nDocument ID: {flag_doc_id}\nClassification: TOP SECRET\n\n<!-- FLAG: {FLAG} -->\n\nThis document contains sensitive information and should only be accessed by authorized personnel.',
        'classification': 'confidential',
        'author': 'System Administrator'
    }
}

for i in range(1000):
    if i not in documents:
        uid = random.randint(0, 9)
        documents[i] = {
            'title': f'Document #{i:03d}',
            'content': f'This is document number {i}.\n\nContent: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\nDocument ID: {i}\nCreated: 2025-01-{(i % 28) + 1:02d}\nAuthor: User{uid}',
            'classification': 'public' if random.randint(0, 2) == 0 else 'internal',
            'author': f'User{uid}'
        }

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/share')
def share():
    return render_template('share.html')

@app.route('/doc/<int:doc_id>')
def view_document(doc_id):
    referer = request.headers.get('Referer', '')
    user_level = request.headers.get('X-User', 'guest')

    if doc_id < 0 or doc_id >= 1000:
        abort(404)
    
    if doc_id not in documents:
        abort(404)
    
    document = documents[doc_id]

    if '/share' not in referer:
        return render_template('error.html', 
            message="Access denied. Documents can only be accessed from the share page."), 403
    
    if document['classification'] == 'confidential':
        if user_level != 'admin':
            return render_template('error.html', 
                message="Insufficient privileges. Administrator access required."), 403
    
    elif document['classification'] == 'internal':
        if user_level == 'guest':
            return render_template('error.html', 
                message="Internal documents require user authentication."), 401
    
    return render_template('document.html', doc=document, doc_id=doc_id)

@app.route('/api/docs')
def list_docs():
    SHOW_COUNT = 15
    user_level = request.headers.get('X-User', 'guest')
    visible_docs = []
    
    for doc_id, doc in documents.items():
        if doc['classification'] == 'public':
            visible_docs.append({'id': doc_id, 'title': doc['title'], 'classification': doc['classification']})
        elif doc['classification'] == 'internal' and user_level != 'guest':
            visible_docs.append({'id': doc_id, 'title': doc['title'], 'classification': doc['classification']})
        elif doc['classification'] == 'confidential' and user_level == 'admin':
            visible_docs.append({'id': doc_id, 'title': doc['title'], 'classification': doc['classification']})
        if len(visible_docs) >= SHOW_COUNT:
            break
    
    return jsonify(visible_docs)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=False)

docunents 라는 변수에 FLAG가 담겨있는 게시글이 있음을 알 수 있다.

FLAG 가 담긴 게시글을 찾아내면 된다!

그럼 특정한 id의 docs를 읽는 라우터를 확인해보자


@app.route('/doc/<int:doc_id>')
def view_document(doc_id):
    referer = request.headers.get('Referer', '')
    user_level = request.headers.get('X-User', 'guest')

    if doc_id < 0 or doc_id >= 1000:
        abort(404)
    
    if doc_id not in documents:
        abort(404)
    
    document = documents[doc_id]

    if '/share' not in referer:
        return render_template('error.html', 
            message="Access denied. Documents can only be accessed from the share page."), 403
    
    if document['classification'] == 'confidential':
        if user_level != 'admin':
            return render_template('error.html', 
                message="Insufficient privileges. Administrator access required."), 403
    
    elif document['classification'] == 'internal':
        if user_level == 'guest':
            return render_template('error.html', 
                message="Internal documents require user authentication."), 401
    
    return render_template('document.html', doc=document, doc_id=doc_id)
    

확인해보면 id가 0 보다 크고 1000 이하여야 하고

document['classification'] == 'confidential' 이면

user_leveladmin이 아니면 안보여준다.

우리 FLAG가 담겨있는 게시글은 'classification' 단계이기 때문에 user_leveladmin으로 맞춰주면 되겠다.

그리고 우리 FLAG가 담겨있는 게시글은 100부터 999 사이의 랜덤한 id 를 찾아내면 되겠다.

100부터 999사이라는 값은 매우 브루트 포스를 넣기에 좋은 상황을 의도적으로 연출 해주었기에 브루트 포스를 해주지 않는다면 상도덕에 어긋나는 것일 것이다. 아마도

Burp Suite로 브루트 포스 요청 넣는법

이번엔 새로운 도구인 Cadio로 Automate를 해보았다.

Burp Suite보다 빨라서 더 좋은듯, ( 물론 설정을 할 수 있었겠지만 )

서로 있을 건 다 있기 때문에 더 직관적인 Cadio를 쓸 것 같다.

어쨌든 FLAG가 담긴 id 번호도 알아냈으니 FLAG도 Response 에서 찾으면

성공

profile
여러가지 공부하는 Velog

1개의 댓글

굳👍 굳👍

답글 달기