0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

picoCTF-Most Cookie writeup

0
Posted at

きっかけ

cookieの脆弱性をついた攻撃があんまり理解できていなかったため、今回はMost Cookieというみるからにcookieが関係していそうな問題を解いてみました。

問題名:Most Cookie

問題を起動するとサイトのリンクが張ってあります。

リンク先の画面
スクリーンショット (237).png

cookieの検索ページだと書いてあり、入力できるようになっています。
試しにtestと入力すると正しいcookieではないと表示されました。
スクリーンショット (238).png

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

成功する文字とそうでない文字がありそうだなと考えられます。

ページのソースを確認してみましたが、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"])に入ることができました。
スクリーンショット (240).png
しかし、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を使っていきます。

スクリーンショット (243).png

{"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という名前のファイルを作りました。
こんな感じです↓
Screenshot_2026-02-11_20_26_02.png
次にflask-unsignのコマンドを打っていきます。

flask-unsign --unsign --cookie "セッションID" --wordlist 辞書名

実行するとsecret_keyがwaferであることが判明しました。
Screenshot_2026-02-11_22_23_56.png

次にsecret_keyを使ってセッションIDを作成していきます。

flask-unsign --sign --cookie "{'very_auth':'admin'}" --secret "wafer"

Screenshot_2026-02-11_22_23_66.png
実行するとvery_authがadminに変更されているセッションIDが生成されました。

セッションIDの活用

サイトのセッションIDを削除して、作成した新しいセッションIDを書き込んでいきます。
スクリーンショット (242).png

書き込めたら、画面を更新するとフラグが表示されました!
スクリーンショット (241).png

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?