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?

GOのechoでcsrfにsamesite属性がつけられない

0
Posted at

はじめに

GOの修正対応プロジェクトにて、セキュリティ向上のためcsrfトークンにsamesite属性を付与するよう依頼されたのですが、躓いたため共有します。

EchoV4.2.0 以降ではCSRFConfigにCookieSameSite設定がすでに追加されているため、あくまでもそれ以前のバージョンでの一時的な解決策となります。

そもそもsamesite属性とは?(知っている方は飛ばしてください)

SameSite属性とは、Webブラウザがクッキーを送信する範囲を制御するための設定です。主にクロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐために利用されます。

Strict: 同一サイトからのリクエストでのみクッキーを送信する(最も厳格)。

Lax: 基本的には同一サイトのみだが、トップレベルナビゲーション(リンク遷移など)などの安全なリクエストでは他サイトからでも送信される(現在のブラウザのデフォルト挙動)。

None: 制限なし(Secure 属性が必須)。

本題CSRFWithConfigではsamesite属性がつけられない

ご存知の通り、Echoを使用してミドルウェアにてCSRFトークンを定義する際には middleware.CSRFWithConfig() にて下記のように定義することが可能です。

このConfigの中に CookieSameSite: http.SameSiteLaxMode のように記載できれば良いのですが、v4.1.13以前のEchoでは定義自体が存在せず、コンパイルエラーとなるため不可能です。

// CSRF設定
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
    TokenLength:    32,
    TokenLookup:    "form:x-csrf-token",
    ContextKey:     "csrf",
    CookieName:     "csrf",
    CookieMaxAge:   0, 
    CookieSecure:   true, 
    CookieHTTPOnly: true,
    //ここにsamesiteの記載は出来ない
}))

ダメだった解決策

最初単純に、クッキー設定したなら、そのままクッキー取得 → samesite属性上書きしたら終わりやんと思っていました。

しかし、結論から言うと c.Cookie("csrf") はあくまでリクエストのクッキー(ブラウザから送られてきたもの)を取得する処理です。 CSRFWithConfig で設定されるCSRFクッキーはレスポンスヘッダー(これからブラウザに送るもの)に設定されているため、この方法では取得・変更ができませんでした。

// CSRF設定
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
    TokenLength:    32,
    TokenLookup:    "form:x-csrf-token",
    ContextKey:     "csrf",
    CookieName:     "csrf",
    CookieMaxAge:   0, 
    CookieSecure:   true, 
    CookieHTTPOnly: true,
    //ここにsamesiteの記載は出来ない
}))

csrf, _ := c.Cookie("csrf")
//❌ここでメモリ上からcsrfトークンのクッキーを取得して上書きすればええやん?

なので下記のようにレスポンスヘッダーからSet-Cookieを取得し直接書き換えをする必要があります。

// CSRF設定
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
    TokenLength:    32,
    TokenLookup:    "form:x-csrf-token",
    ContextKey:     "csrf",
    CookieName:     "csrf",
    CookieMaxAge:   0, 
    CookieSecure:   true, 
    CookieHTTPOnly: true,
    // ※Echoのバージョンが古い等の理由で CookieSameSite が設定できない場合
}))

// 【修正版】CSRF CookieにSameSite=Laxを強制的に追加するミドルウェア
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        // レスポンス書き込み前にフックする
        c.Response().Before(func() {
            cookies := c.Response().Header()["Set-Cookie"]
            if len(cookies) == 0 {
                return
            }

            // 書き換えが発生したかどうかのフラグ
            modified := false
            newCookies := make([]string, 0, len(cookies))

            for _, cookie := range cookies {
                // クッキー名が "csrf" で始まり、かつ SameSite 属性がない場合のみ追記
                // ※CookieName設定を変えた場合はここも変更が必要
                if strings.HasPrefix(cookie, "csrf=") && !strings.Contains(strings.ToLower(cookie), "samesite") {
                    cookie += "; SameSite=Lax"
                    modified = true
                }
                newCookies = append(newCookies, cookie)
            }

            // 書き換えがあった場合のみヘッダーを更新
            if modified {
                c.Response().Header().Del("Set-Cookie")
                for _, cookie := range newCookies {
                    c.Response().Header().Add("Set-Cookie", cookie)
                }
            }
        })

        return next(c)
    }
})

c.Response().Before(...): ステータスコードやヘッダーが書き込まれる直前に処理をフックします。

strings.HasPrefix(cookie, "csrf="): 全てのクッキーを書き換えないよう、対象のCSRFクッキーのみを判定します。

文字列操作: 既存のクッキー文字列のお尻に "; SameSite=Lax" を追記します。

これで、古いバージョンのEchoでも強制的にSameSite属性を追加することができました!

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?