ペネトレーションしのべくん

さようなら、すべてのセキュリティエンジニア

SECCON CTF for Beginners 2022 Web 「Ironhand」Writeup

はじめに

ctf4bおつかれさまでした。運営の皆様もいつもありがとうございます。 IronhandのWriteupがぱっと見なかったので書きます。

score.beginners.azure.noc.seccon.jp

概要

  • ログインするとJWTが与えられる
  • IsAsminをtrueにしたJWTを持っている状態だと、flagが閲覧できる
  • 署名はappの環境変数JWT_SECRET_KEYによって行われている
  • appの/staticAPIにはディレクトリトラバーサル脆弱性があり、これを利用して/proc/self/environにアクセスし、JWT_SECRET_KEYを手に入れる

詳細

何もせずログインすると以下のような画面が表示されます。

Cookieとして付与されたJWTをデコードすると、IsAdminがfalseです。

appのコードのこの辺を読むと、このIsAdminがtrueならflagが表示される事がわかります。

if claims.IsAdmin {
    res, _ := http.Get("http://secret")
    flag, _ := ioutil.ReadAll(res.Body)
    if err := res.Body.Close(); err != nil {
        return c.String(http.StatusInternalServerError, "Internal Server Error")
    }
    return c.Render(http.StatusOK, "admin", map[string]interface{}{
        "username": claims.Username,
        "flag":     string(flag),
    })
}

同じくappのコードの以下の部分、本来ならstatic/以下のファイルを探すためのコードっぽいですが、パストラバーサルに利用できそうな気がします。Nginxの設定ファイルに見慣れないオプション(merge_slashes off;)があることも気になります。

e.GET("/static/:file", func(c echo.Context) error {
    path, _ := url.QueryUnescape(c.Param("file"))
    f, err := ioutil.ReadFile("static/" + path)
    if err != nil {
        return c.String(http.StatusNotFound, "No such file")
    }
    return c.Blob(http.StatusOK, mime.TypeByExtension(filepath.Ext(path)), []byte(f))
})

色々試してみると、以下のようにパストラバーサル脆弱性があることが分かりました。

脆弱性を利用して/proc/self/environへアクセスすることで、そのプロセスの環境変数を確認します。

JWT_SECRET_KEYが含まれています。

ここで得たJWT_SECRET_KEYを使って、IsAdminがtrueなJWTを作成します。画像のすぐ下に秘密鍵を入力するフォームがあるので、JWT_SECRET_KEYの値を入力します。「secret base64 encoded」はチェックしません。

出力されたJWTをブラウザのクッキーとして設定して更新すると、flagが表示された画面に遷移します。