クロスサイトリクエストフォージェリ (CSRF) 対策
トークンを用いた防御手法と、Goでのミドルウェア実装。
1. CSRF攻撃の基本
CSRFは、Cross-Site Request Forgeryの略で、ユーザーが認証情報(セッションCookie)を持つ状態で、悪意のある外部サイトからそのユーザーになりすまして意図しないリクエスト(送金、パスワード変更などのCRUD操作)を実行させる攻撃です。
POST/PUT/DELETE には必ず CSRFトークン をつけます。
要は、ご本人のリクエストかどうか?を証明する実装が必須。
Go言語によるCSRF対策の実装
ご提示いただいたGo言語のコードを、機能別に分類し解説します。
2-1. CSRFトークン生成とセッション保存
トークンは予測不能でなければなりません。暗号論的に強力な乱数生成器を使用します。
// 1. 超強い秘密の合言葉(トークン)を作る関数
func generateCSRFToken() string {
b := make([]byte, 32) // 32バイト=めっちゃ長いランダムな数字を作る
rand.Read(b) // 完全ランダムに!!
return base64.StdEncoding.EncodeToString(b) // それを文字に変換して返す
}
// → 例: "aBcDeFgHiJkLmNoPqRsTuVwXyZ123456" みたいな超長い合言葉ができる!
// ログインしたときにやること
token := generateCSRFToken() // 秘密の合言葉を作る
session.Values["csrf_token"] = token // セッション(ユーザーのメモ帳)に覚えさせる
session.Save(r, w) // メモ帳を保存!
2-2. HTMLフォームへのトークン埋め込み
通常のフォーム送信と、JavaScript(Ajax)からの送信の両方に対応します。
<!-- 2. HTMLに秘密の合言葉を2箇所にこっそり書く -->
<meta name="csrf-token" content="aBcDeFgHiJkL..."> <!-- JavaScriptが読めるように -->
<form method="POST">
<input type="hidden" name="csrf_token" value="aBcDeFgHiJkL..."> <!-- 普通のフォーム用 -->
<button>送信</button>
</form>
<!-- → 悪者のサイトにはこの合言葉が書かれてない! -->
2-3. 門番としてのミドルウェア検証
すべての変更系リクエスト(POST, PUT, DELETEなど)が、このチェックを通過するように設定します。
// 3. ミドルウェア=「門番おじさん」
// 全部のリクエストをここでチェック!
func CSRFMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// GET(見るだけ)は安全だからスルー
if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
next.ServeHTTP(w, r)
return
}
// 合言葉を持ってきて!
token := r.FormValue("csrf_token") // フォームから探す
if token == "" {
token = r.Header.Get("X-CSRF-Token") // なければヘッダーから探す(Ajax用)
}
// セッションから正しい合言葉を取り出す
session, _ := store.Get(r, "session")
expected := session.Values["csrf_token"]
// 合言葉が空 or 間違ってる → 即ドア閉める!!
if token == "" || expected != token {
http.Error(w, "合言葉が違う!入れないよ!", http.StatusForbidden)
return
}
// 合言葉OK!通してあげる
next.ServeHTTP(w, r)
})
}
2-4. SameSite属性による追加防御
SameSite Cookie属性は、ブラウザレベルでクロスサイトリクエスト時にCookieを送信するかどうかを制御します。これはトークンチェックとは別に、最も簡単で強力な防御策の一つです。
// 4. クッキーに「他サイトからは来るな!」の札をつける
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sessionID,
HttpOnly: true, // JavaScriptから読めない
Secure: true, // HTTPSのみ
SameSite: http.SameSiteLaxMode, // ← 悪者のサイトからクッキー送れない!!
})
<!-- AjaxでPOSTするときの書き方(超大事!) -->
<script>
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch("/delete", {
method: "POST",
headers: {
"X-CSRF-Token": token // ← ここに合言葉を入れる!!
}
})
</script>
・SameSite: Lax:トップレベルナビゲーション(リンククリックなど)経由のGETリクエスト以外では、クロスサイトのCookie送信をブロックします。多くのCSRF攻撃を防ぎます。
・SameSite: Strict:同じサイト内からのリクエスト以外では、一切Cookieを送信しません。最も安全ですが、ユーザー体験に影響が出る可能性あります。
3. CSRF対策チェックリストとWebセキュリティの基本
| 対策カテゴリ | 項目 | 目的と効果 |
|---|---|---|
| CSRFトークン | トークンは32byte以上のランダムか? | 予測されるのを防ぐ |
| トークンをセッションに保存し、リクエストと一致比較しているか? | 本人からのリクエストであることを証明 | |
| フォームとAjaxヘッダーの両方に対応しているか? | すべての変更操作経路をカバー | |
| SameSite Cookie | セッションCookieにSameSite=LaxまたはStrictを設定しているか? | ブラウザにCookieの自動送信を制限させ、広範囲のCSRFをブロック |
| ミドルウェア | POST/PUT/DELETEは全てCSRFMiddlewareを通っているか? | 変更操作を伴うリクエストを漏れなく防御 |
Webセキュリティの基本3大脆弱性まとめ
| 情弱性 | 対策 | Go言語での対応例 |
|---|---|---|
| SQLインジェクション (SQLi) | プリペアドステートメントの使用 | ?プレースホルダによるバインド変数の利用 |
| クロスサイトスクリプティング (XSS) | 出力時の適切なエスケープ処理 | html/templateパッケージの使用 |
| クロスサイトリクエストフォージェリ (CSRF) | SameSite CookieとCSRFトークンの併用 | SameSiteLaxModeと上記ミドルウェアの実装 |