はじめに
こんにちは。ペンギンになりたいエンジニアの島ノ江です。
現在は FutureVuls という脆弱性管理サービスの開発・営業などを担当しています。
Go 1.25リリース連載の4本目、マイナーアップデートの net/http での CSRF 対策の強化について触れます。
この記事では、以下の内容について触れていきます。CSRFについて既知の場合はリリースの実装内容の項目を参照してください。
関連する issue はこちらです(issue)
今回のリリースの概要
新たに net/http に CrossOriginProtection
が実装され、安全でない Cross-Origin ブラウザリクエストを拒否することで、CSRF から保護するようになりました。これまでの Go では、標準ライブラリの net/http に CSRF 対策用のハンドラは存在しなかったため、外部ライブラリの利用や開発者が一から実装する必要がありました。今回のリリースにより、これらが標準ライブラリとして含まれるようになりました。
具体的には、fetch meta data リクエストヘッダーである Sec-Fetch-Site
ヘッダーの確認、または Origin
ヘッダーのホスト名と Host
ヘッダーを比較することで検出します。これにより、従来 CSRF 対策として利用されていたトークンや Cookie を必要とせずに、オリジンベース及びパターンベースで CSRF の対策ができるようになりました。
詳細な判定ロジックは後述します。
CSRFについて
Cross-Site Request Forgery(CSRF) とは、Web アプリケーションの脆弱性を利用した攻撃手法の1つです。Web サイト側が、ログインした利用者からのリクエストについて、その利用者が意図したリクエストであるかどうかを識別する仕組みを持たない場合に、ブラウザが Cookie を自動送信する性質が悪用されて、悪意のあるリクエストを受け付けてしまう場合があります。この脆弱性があると、ユーザが意図していない操作を Web サイト上で強制的に実行させることができます。
「Forgery」とは和訳すると「偽造」という意味で、文書や署名を偽造する行為を指します。
CSRFの攻撃条件
基本的に以下の3要素が揃うと攻撃条件が成立します。
- ユーザが認証済みの Web サイトが存在する:
ユーザは既にログインしており、セッションが有効な状態である - Webサイトが状態を変更するリクエストを、ユーザからの認証情報のみで判断している:
サーバ側が、リクエストの送信元が本当にそのユーザ本人であるかの確認を行っていない - 攻撃者が、ユーザに悪意のあるWebページやメールを閲覧させる:
攻撃者は、ユーザが認証済みのサイトに対して悪意のあるリクエストを仕込んだページを用意し、ユーザを誘導する
IPAに解説があるため、この図を引用します。

CSRFの対策
CSRF には古典的に以下のような方法で対策をとれます。
- CSRFトークンの利用:
すべての入力フォームに手動でトークンを埋め込み、都度検証する方法。ただし、各フォームで設定する必要があり、実装が面倒に問題がある Origin
ヘッダーのチェック:
リクエストの送信元オリジンを確認する方法。ただし、リバースプロキシ環境などで設定が複雑になる問題がある- SameSite 属性クッキーの利用:
Cookie に属性を設定し、クロスサイトリクエスト時にクッキーを送付するかどうかを制御する方法。ただし、一部のSSOフローで壊れる問題がある
CSRFの最近の動向
CSRF はWebサイト全体のセキュリティ向上に伴い、近年は被害件数が減少傾向にあります。
その背景は主に以下の通りです。
- Cookie の SameSite 属性の自動設定など、モダンブラウザでの自動対応:
Webサイト側で明示的な対策をしなくても、ブラウザ側で多くの CSRF が自動的にブロックされるようになりました - Webフレームワーク側での対策の自動化:
開発者が意識して一から実装する必要はなくなりました。今回のリリース内容もこれに分類されます - SPAとAPI通信を組み合わせた方式の普及:
SPA では Cookie による認証を使わずに、リクエストヘッダーに JWT のような認証トークンを入れる方式を採ります。そのため、そもそもの CSRF 攻撃の前提が成り立たなくなっています
上記のような対応が進んだことで、CSRF の脅威度は大きく下がりました。しかし、古いシステムや対策が不十分なWebサイトでは依然としてこのリスクは残るため、基本的なセキュリティ対策は引き続き必要となります。
提案の背景
これまで、CSRF 対策を目的とした Go のライブラリでは、例えば以下の2つが広く利用されていました。
gorilla/csrf では、上述の対策のうち1.CSRFトークンによる検証と2.Origin ヘッダーの検証で CSRF の対策をしていました。ただし、issue の提案者はこのライブラリに関して、Origin ヘッダーの検証に関するバグがある問題や、脆弱性の報告から修正までの対応が遅いなどの問題を指摘しています。
そこで、このようなライブラリに外部依存していた部分を標準ライブラリでサポートすることで、メンテナンス性やセキュリティアップデートをしやすくしたのが今回のアップデート対応です。
実装内容
CrossOriginProtection
構造体
ベースとなる struct は以下のようになっています。
type CrossOriginProtection struct { |
設定項目 | 概要 | 設定メソッド |
---|---|---|
trusted (信頼済みオリジン) |
安全とわかっている送信元オリジンを登録する | AddTrustedOrigin |
bypass (チェックの回避) |
CSRFチェックを完全に無効化するURLパスのパターン。公開APIなど、意図的に保護対象外にしたい場合に利用する | AddInsecureBypassPattern |
deny (拒否処理) |
リクエストブロック時の処理の設定 | SetDenyHandler |
具体的に使う際は、各メソッドで設定し、ハンドラを CrossOriginProtection
で wrap します。
各フィールドを設定しない場合はデフォルトの設定で安全に動作します。
mux := http.NewServeMux() |
Cross-Originの判定方法
リクエストが Cross-Origin かどうかは Check
メソッドで判定されます(実装)。判定ロジックを詳しくみていきます。
GET
,HEAD
,OPTIONS
のリクエストは、サーバの状態を変更しない安全なメソッドのため、常に許可されます- 次に、リクエストヘッダーの
Sec-Fetch-Site
ヘッダーを優先的に確認します""
の場合(Sec-Fetch-Site
に対応していない古いブラウザからのアクセス、curl
によるリクエストなど):後述のOrigin
の比較に移りますsame-origin
,none
の場合:同一オリジンからのアクセスで安全と判断して許可します- 上記以外の
cross-site
などの場合は、例外ルールに当てはまるかをisRequestExempt
メソッドで確認します。下記に当てはまる場合は、安全と判断します- リクエストのパスが、Cross-Originチェックを無視する「バイパスリスト(
bypass
)」に設定されている - リクエストの送信元が、安全だとわかっている送信元を記録する「信頼済みオリジンリスト(
trusted
)」に設定されている
- リクエストのパスが、Cross-Originチェックを無視する「バイパスリスト(
Sec-Fetch-Site
で判定ができない場合、リクエストヘッダーのOrigin
ヘッダーを確認します""
の場合:同一オリジンリクエストか非ブラウザリクエストと判定して許可しますOrigin
ヘッダーと、Host
ヘッダー(リクエスト先のホスト名)が異なる場合、Cross-Origin と判断します
- 最後に、危険な可能性をはらむリクエストについて、
isRequestExempt
メソッドで例外的に許可できないかを確認します
なお、issue にも書かれていますが、この Origin
と Host
の比較をする方法には問題があります。それは、 Host
ヘッダーには scheme(http / https)が含まれないため、「http://
から https://
へのリクエストを区別できない」というものです。
例えば…
Origin
ヘッダー:http://example.com
Host
ヘッダー:example.com
- リクエスト URL :
https://example.com/
…のようなアクセスをする場合、Origin
ヘッダーとHost
ヘッダーは example.com
で一致するため、このチェックを通過してしまいます。しかし、http
と https
は異なるオリジンであり、本来はブロックするべきクロスオリジンリクエストです。
ただ、実装上この判定ロジックに回るのは、Sec-Fetch-Site
に対応していない古いブラウザでアクセスした場合です。そのため、この次善策では fail-open として、リクエストをブロックして安全側に倒すのではなく、利便性のために甘く判定しています。
なお、http
→ https
のクロスオリジンについては、HSTS(HTTP Strict Transport Security)を利用して、ブラウザが常に https
でアクセスするように設定することで解決できます。そもそも http://
のような暗号化されていないページでは、データを送信する前の段階で中間者攻撃にあう可能性があるため、セキュリティ上問題があります。
さいごに
今回はマイナーアップデートの内容をテーマに、Web セキュリティに関する CSRF 周りの理解を深めてみました。Cookie 認証の問題点や Sec-Fetch-Site
などに関する理解を深められて、とても興味深かったです。
セキュリティの基本として、多層防御の観点は重要です。ブラウザの SameSite
属性を設定したうえで、さらにサーバー側で明示的にリクエストを検証することで、より堅牢なシステムを実現できます。セキュリティ対策の実現の学習としても調べていて面白かったです。
ご覧いただきありがとうございました。