Open roaris opened 4 months ago
schema.sqlを見ると、todosテーブルの最初のレコードがフラグになっている 定期的にGET /api/list/user5773E6Ba/?secret=C76e8A41F907Eeeというようなリクエストが送信されるので、user5773E6Baのところをallに書き換えて、リクエストを送信すると、フラグが得られた
そこそこコード量があるけど、ほぼ読まずに解けてしまったので少し読む
まず、routes.py コメントからGET /list/allが怪しいと分かる
from flask import Blueprint, request, jsonify, session, render_template, g
from application.util import verify_integrity
from application.models import todo
main = Blueprint('main', __name__)
api = Blueprint('api', __name__)
@main.route('/')
def index():
context = {
'list_access': g.user,
'secret': todo.get_secret_from(g.user)
}
return render_template('index.html', **context)
@api.before_request
@verify_integrity
def and_then(): pass
# TODO: There are not view arguments involved, I hope this doesn't break
# the authentication control on the verify_integrity() decorator
@api.route('/list/all/')
def list_all():
return jsonify(todo.get_all())
@api.route('/list/<assignee>/')
def list_tasks(assignee):
return jsonify(todo.get_by_user(assignee))
@api.route('/add/', methods=['POST'])
def add():
todo.add(g.name, g.user)
return {'success': f'Successfuly added {g.name} by user {g.user}'}
@api.route('/rename/<int:todo_id>/<new_name>/')
def rename_task(todo_id, new_name):
g.selected.rename(new_name)
return {'success': f'Successfuly edited {todo_id} to {new_name}'}
@api.route('/delete/<int:todo_id>/', methods=['DELETE'])
def delete(todo_id):
g.selected.delete()
return {'success': f'Successfuly deleted {todo_id}'}
@api.route('/complete/<int:todo_id>/')
def complete(todo_id):
g.selected.complete()
return {'success': f'Successfuly completed {todo_id}'}
@api.route('/assign/<int:todo_id>/<new_assignee>/')
def assign(todo_id, new_assignee):
g.selected.reassign(new_assignee)
return {'success': f'Successfuly reassigned {todo_id} to {new_assignee}'}
次にutil.pyのverify_integrity
def verify_integrity(func):
def check_secret(secret, name):
if secret != todo.get_secret_from(name):
return abort(403)
@functools.wraps(func)
def check_integrity(*args, **kwargs):
g.secret = request.args.get('secret', '') or request.form.get('secret', '')
if request.view_args:
list_access = request.view_args.get('assignee', '')
if list_access and list_access != g.user:
return abort(403)
todo_id = request.view_args.get('todo_id', '')
if todo_id:
g.selected = todo.get_by_id(todo_id)
if g.selected:
if dict(g.selected).get('assignee') == g.user:
check_secret(g.secret, g.user)
return func(*args, **kwargs)
return abort(403)
return abort(404)
if request.is_json:
g.task = request.get_json()
g.name = g.task.get('name', '')
if g.name and len(g.name) <= 100 and not re.search('script|meta|link|src|on[a-z]', g.name, re.IGNORECASE):
g.name = g.name.replace('<', '<').replace('>', '>')
check_secret(g.task.get('secret', ''), g.user)
return func(*args, **kwargs)
return abort(400)
check_secret(g.secret, g.user)
return func(*args, **kwargs)
return check_integrity
GET /list/\<assignee>/の場合は、セッションで決まるユーザIDとパスパラメータ中のユーザIDが一致しないと403エラーとなる
g.user
というのは、app.pyのis_authenticatedで代入されている
セッションで決まるユーザIDとパスパラメータ中のユーザIDが一致していても、リクエスト中で指定したsecretとユーザIDに紐づいているsecretが一致しなければ403エラーとなる
@app.before_request
def is_authenticated():
g.user = session.get('authentication')
if not g.user:
username = f'user{generate(8)}'
todo.create_user(username, generate(15))
g.user = session['authentication'] = username
GET /rename/
POST /api/addを呼び出すと、if request.is_json
に引っかかり、scriptタグなどが含まれていると400エラーとなる
また<と>がエスケープされている(出力時にエスケープするのが一般的な気がするが)
https://app.hackthebox.com/challenges/baby%2520todo%2520or%2520not%2520todo