Scribble at 2025-12-25 09:43:44 Last modified: unmodified

添付画像

First of all, in some cases subdomains sharing the same registered domain may operate independently, and as such, it is not out of the question that one subdomain may attempt to attack another through CSRF. Depending on the level of trust an application has for other subdomains, a server may want to block requests that come with the Sec-Fetch-Site header set to same-site. In Microdot, I have added an argument allow_subdomains to cover this case. I decided to err on the side of security, so the default is False, meaning that requests from subdomains are also blocked.

CSRF Protection without Tokens or Hidden Form Fields

まず基本から説明すると、WWW ブラウザというアプリケーションはウェブ・サーバとのリクエストにおいて Cookie を自動で送出するように作られている。これは、HTTP というプロトコルがステートレスだからだ。要するに、リクエストは個別に処理されるだけの独立したデータにすぎず、前後の脈絡や条件関係なんて全くないのだ。したがって、僕らがブラウザで或るウェブ・ページを表示したとき、まず受け取った index.html などを解析して、次にウェブ・サーバへ「ヘッダー画像の header.jpg をレスポンスせよ」というリクエストが送られ、そしてその次に「ページをレンダリングするのに使う main.css をレスポンスせよ」というリクエストが送られたときでも、heaader.jpg のリクエストと main.css のリクエストは、ウェブ・サーバにとっては個々のバラバラなリクエストにすぎないので、それが「同じブラウザ」から送られたリクエストなのかどうかは関知していない。すると、たとえば銀行のサイトなどでログインした後に「10万円をこれこれの口座へ送金せよ」というリクエストを送るとして、これがログインした僕らの使っているブラウザから送られたリクエストであることを保証するには、どうすればいいだろうか。これが保証できないと、たとえば全く別のサイトにアクセスしたときに、そのサイトが悪意のあるコードを動かしていたとすれば、あなたの銀行のサイトに対して「100万円を俺の口座に振り込め」というリクエストを勝手に送らされてしまうような命令を僕らのブラウザから発してしまうかもしれない。これが「CSRF (Cross-Site Request Forgery) 攻撃」と呼ばれている手法だ。

このような攻撃に対する標準的な対策は、「僕が発したリクエストだけを処理せよ」という制限を加えるために、トークンと呼ばれる一意の文字列を発行して、リクエストごとに <input /> の値とサーバ側で保管している値とを照合し、一致すれば正常なリクエストだと判定するというやり方がある。攻撃者がしかけた別のサイトでは、こういう正しいトークンは取得できないので、まず初等的な防御としては信頼できる。それから、サーバ側で値を保持したくない場合には、<input /> の値と Cookie にセットした値とを比較するやり方もある。他に、もっと古臭い手法として "referrer"(慣習的に、英語としてはミス・スペルなのだが "referer" の値を照合するというものもあるが、これは多くのセキュリティ・ソフトが「プライバシー保護」の名目で、ブラウザからのリクエスト送出に割り込んで値を消してしまう場合があるから問題があった。

これに対して、上のブログ記事で紹介されている "Sec-Fetch-Site" というヘッダを利用した手法は、まったく簡単に導入できて強力だ。最近のブラウザでは、このヘッダを送出するようになっていて、"cross-site" の値として送出されるリクエストをブロックするだけで CSRF 攻撃をブロックできる("same-site" はサブドメインを含むので、もしサブドメインを信頼できないなら、これもブロックした方がよい)。ローカル環境で PHP や Apache を動かしているなら、phpinfo() で表示される情報の "HTTP Headers Information" をご覧いただくと、"Sec-Fetch-Site" ヘッダに "same-origin" がセットされていることを確認できるだろう。PHP で取得する場合は $_SERVER['HTTP_SEC_FETCH_SITE'] を使う。

もちろん、この手法にも幾つかの制約はある。たとえば、2023年までの Safari はヘッダとして "Sec-Fetch-Site" を実装していなかったので、こういう古いブラウザを使っているユーザには利用できない。それから、他の攻撃との組み合わせには万全な対策とはならない。現在だと、RPA の実行環境をウイルスに仕込まれて、まさしく本人のブラウザ操作をシミュレートして勝手にリクエストを送られてしまうといった攻撃も出てきている。なので、本当に重要な操作では、やはり MFA などの併用が有効だろう。

  1. もっと新しいノート <<
  2. >> もっと古いノート

冒頭に戻る