きっかけ
cookieの脆弱性をついた攻撃があんまり理解できていなかったため、今回はMost Cookieというみるからにcookieが関係していそうな問題を解いてみました。
問題名:Most Cookie
問題を起動するとサイトのリンクが張ってあります。
cookieの検索ページだと書いてあり、入力できるようになっています。
試しにtestと入力すると正しいcookieではないと表示されました。

右上にあるリセットボタン押した後、今度はデフォルトで表示されているsnickerdoodleを入力してみるとなんか成功している?…

成功する文字とそうでない文字がありそうだなと考えられます。
ページのソースを確認してみましたが、HTMLやCSSなどが見られるだけでcookieの定義がどこにも書いてありません。
flaskのpythonコードに書いてあると思うので見てみようと思いましたが、ソースは張られていないという不思議な現象が発生していました。
※picoCTFサイトを再起動しても表示されませんでした。
しょうがないのでWriteupを書いている人のサイトからpythonのソースコードを拝見させていただきました。
※参照したサイトのURL:https://tech.kusuwada.com/entry/2021/04/08/121028
flaskのpythonコード↓
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)
@app.route("/")
def main():
if session.get("very_auth"):
check = session["very_auth"]
if check == "blank":
return render_template("index.html", title=title)
else:
return make_response(redirect("/display"))
else:
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/search", methods=["GET", "POST"])
def search():
if "name" in request.form and request.form["name"] in cookie_names:
resp = make_response(redirect("/display"))
session["very_auth"] = request.form["name"]
return resp
else:
message = "That doesn't appear to be a valid cookie."
category = "danger"
flash(message, category)
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
@app.route("/reset")
def reset():
resp = make_response(redirect("/"))
session.pop("very_auth", None)
return resp
@app.route("/display", methods=["GET"])
def flag():
if session.get("very_auth"):
check = session["very_auth"]
if check == "admin":
resp = make_response(render_template("flag.html", value=flag_value, title=title))
return resp
flash("That is a cookie! Not very special though...", "success")
return render_template("not-flag.html", title=title, cookie_name=session["very_auth"])
else:
resp = make_response(redirect("/"))
session["very_auth"] = "blank"
return resp
if __name__ == "__main__":
app.run()
まずこのコードを見て、@app.route("/display",methods=["GET"])の所にflagが隠されていそうなflag.htmlへの移動が書いてあることに注目しました。
問題サイトを開いたときは@app.route("/")にいるため、画面上のURLバーの表記を/displayに変更してあげると@app.route("/display",methods=["GET"])に入ることができました。

しかし、if check == "admin": の条件を満たしていないため、not-flag.htmlの結果が反映されていそうですね。
セッションID情報の"very_auth"がadminでないとflag.htmlを表示しないっぽい…
セッションIDがどうなっているのか確認していく必要があります。
flaskのセッションIDについて
まず、flaskのセッションIDについて説明していきます。
構成がデータの中身とタイムスタンプ、署名(HMAC)の3つで出来ています。
これらは .(ドット)で区切られており、データの中身は暗号ではなくbase64でエンコードされています。
実際に問題サイトのセッションIDを確認していきます。
確認方法:サイト内で右クリック→検証→アプリケーション→cookie
sessionID = eyJ2ZXJ5X2F1dGgiOiJibGFuayJ9.aY1Fuw.5YMzSc4CF6ri1e3TQen9HptZZL4
この文字列のeyJ2ZXJ5X2F1dGgiOiJibGFuayJ9がデータの中身です。
kaliLinuxのターミナルでデコードする方法もありますがcyberchefを使った方が楽なのでcyberchefを使っていきます。
{"very_auth":"blank"}と出力され、very_authがblankになっていることが分かります。
この部分をadminに書き換えたセッションIDを作成して送信してあげれば if文の条件を達成し、フラグが表示されそうですね。
セッションIDの作成
flaskではセッションIDの署名(HMAC)作成にsecret_keyを使っています。
今回の問題ではsecret_keyがcookie_nameの中からランダムに選ばれる仕組みとなっているため、使われているsecret_keyを特定する必要があります。
どうやってsecret_keyの特定すればいいのかと考えた時にflask-unsignというツールがあることを知りました。
flask-unsignとは?
Flask-unsignは、FlaskセッションCookieを分析および操作するためのセキュリティテストツールです。
※参照元URL:https://linuxcommandlibrary.com/man/flask-unsign#:~:text=DESCRIPTION,violates%20laws%20like%20the%20CFAA.
このツールを使うと簡単にsecret_keyの特定とセッションIDの作成ができます。
kaliLinuxには標準インストールされていないため、ターミナルでpipかpipxでインストールする必要があります。
※インストール方法は別のページで解説します。
flask-unsignをインストールしたら、いよいよsecret_keyの特定作業(辞書攻撃)をしていきます。
まず、secret_keyの候補がpythonコードに書かれているのでそれを使って辞書を作ります。
私はvimでmost_cookieという名前のファイルを作りました。
こんな感じです↓

次にflask-unsignのコマンドを打っていきます。
flask-unsign --unsign --cookie "セッションID" --wordlist 辞書名
実行するとsecret_keyがwaferであることが判明しました。

次にsecret_keyを使ってセッションIDを作成していきます。
flask-unsign --sign --cookie "{'very_auth':'admin'}" --secret "wafer"

実行するとvery_authがadminに変更されているセッションIDが生成されました。
セッションIDの活用
サイトのセッションIDを削除して、作成した新しいセッションIDを書き込んでいきます。

ここまで閲覧していただき、ありがとうございます。


