如果仔細看登入的函式:
@app.route('/login/user', methods=['POST'])
def do_login_user_post():
username = get_required_params("POST", ['login'])['login']
backend.cache_save(
sid=flask.session.sid,
value=backend.get_key_for_user(username)
)
state = backend.check_user_state(username)
if state > 0:
add_msg("user has {} state code ;/ contact backend admin ... ".format(state))
return do_render()
flask.session[K_LOGGED_IN] = False
flask.session[K_AUTH_USER] = username
return do_302("/login/auth")
@app.route("/login/auth", methods=['POST'])
def do_auth_post():
flask.session[K_LOGGED_IN] = False
username = flask.session.get(K_AUTH_USER)
params = get_required_params("POST", ["password", "token"])
hashed = backend.password_hash(params['password'])
record = sql_session.query(model.Users).filter_by(
username=username,
password=hashed,
).first()
if record is None:
add_msg("Fail to login. Bad user or password :-( ", style="warning")
return do_render()
# well .. not implemented yet
if 1 == 0 and not backend.check_token(username, token=1):
add_msg("Fail to verify 2FA !")
return do_render()
flask.session[K_LOGGED_IN] = True
flask.session[K_LOGGED_USER] = record.username
return do_302("/home/")
我們第一次登入是呼 /login/user
,然後再呼 /login/auth
。其中我們要的 key
是在 /login/user
對應的 do_login_user_post
中就用 cache_save()
來存在快取中了,而且這時我們還沒登入成功。
我們回去看一下取 key
的函式:
@app.route("/note/getkey")
@loginzone
def do_note_getkey():
return flask.jsonify(dict(
key=backend.get_key_for_user(flask.session.get(K_AUTH_USER))
))
拿 key
這個函式並不會吃 cache
,但是它很可惜,沒有漏掉 @loginzone
。
不過新增筆記的函式 do_note_add_post()
會吃 cache,用它來新增一個筆記。
因為這程式是可以並行執行的,所以我們的套路大概會是:
如果回去看筆記和自己的 Key,會發現這是一個 XOR Cipher。這題主要是有一個 concurrency bug,故假設我們第二步和第三步中間的時間間隔非常短,那麼我們就會有一篇筆記是用了目標帳號的 key 去加密的。
新增之後,用密文對我們自己的明文再 XOR 一次,就可以得知 key。用這個解開 admin 的筆記,就可以得到 flag。
CTFtime.org / Teaser Dragon CTF 2018 / 3NTERPRISE s0lution / Writeup
Teaser Dragon CTF 2018 – 3NTERPRISE – SIGINT