よく聞く『CSRF』『XSS』、実際どう対策されているか調べてみた。

はじめに

「CSRF」「XSS」——セキュリティの話でよく耳にする言葉です。名前は知っているし「危ないもの」というのは分かるけれど、実際どういう攻撃で、どう対策されているのかを聞かれると、ちゃんと説明できるか自信がありませんでした。

そこで、普段なんとなく使っているブラウザの機能やフレームワークの設定を実際に見に行きながら、「実際どう対策されているか」を調べてみました。この記事では、概念の説明は最小限にして、実際にDevToolsやレスポンスヘッダーを確認しながら進めていきます。

  • ※本記事ではGoogle Chromeを前提に手順を紹介しています。
  • ※本記事内の検証は、自社サービスではなく https://www.yahoo.co.jp を例に、レスポンスヘッダーなどの公開情報のみを確認しています。

XSSとは何か

XSS(クロスサイトスクリプティング)は、悪意のあるスクリプトを他人のブラウザ上で実行させる攻撃です。例えば、掲示板のコメント欄に<script>タグを仕込まれ、そのページを開いた別のユーザーのブラウザでスクリプトが実行されてしまう、というのが典型的なパターンです。

実行されたスクリプトによって、Cookieの盗み出しや、なりすましての操作が行われる危険があります。

XSSの対策、実際どうなってるか調べてみた

1. エスケープ処理

もしこれがなかったら…コメント欄に入力された<script>タグがそのまま実行され、そのページを開いた全員のブラウザで悪意のあるスクリプトが動いてしまいます。

ユーザーが入力した文字列をそのままHTMLに出力すると、<script>のようなタグがそのまま実行されてしまいます。これを防ぐのが「エスケープ処理」で、<や>などの特殊文字を&lt;や&gt;に変換して、タグとして解釈されないようにします。

React/Vueなどのモダンなフレームワークでは、テンプレート内で変数を出力する際にデフォルトでこのエスケープが行われています。普段意識せずに使っていましたが、実はここでも対策が効いていたことに気づきました。

2. CSP(Content-Security-Policy)

もしこれがなかったら…エスケープ処理をすり抜けて何らかのスクリプトが注入されてしまった場合、そのスクリプトが外部の攻撃者のサーバーと自由に通信できてしまいます。

CSPは、そのページでどこから読み込んだスクリプトやリソースを実行を許可するかを指定するレスポンスヘッダーです。仮に攻撃者がスクリプトを注入できたとしても、CSPで許可されていない場所からのスクリプトは実行されなくなります。

実際にサイトのレスポンスヘッダーを見てみます。DevToolsの「Network」タブを開き、トップページのリクエストをクリックして「Headers」タブの「Response Headers」を確認します。

DevToolsのResponse HeadersでContent-Security-Policyヘッダーを確認している画面

curlでも同様に確認できます。

curl -I https://www.yahoo.co.jp

Content-Security-Policyヘッダーが存在するか、どんな値が設定されているかを見ることで、そのサイトがどこまでスクリプトの実行元を制限しているかがわかります。

そういえば、以前CORSエラーの調査をしていたときも、同じ「Response Headers」を見ていました。あのときはAccess-Control-Allow-Originを探していましたが、実はすぐ隣にCSPやSameSiteの設定も一緒に表示されていたんですよね。当時は素通りしていましたが、今回調べてみて「あれも実はセキュリティの設定だったのか」と繋がりました。

3. CookieのHttpOnly属性

もしこれがなかったら…注入されたスクリプトがdocument.cookieを読み取り、ログイン中のセッション情報を攻撃者のサーバーに送信できてしまいます。

XSSでスクリプトが実行されてしまった場合でも、CookieにHttpOnly属性が付いていれば、JavaScriptからそのCookieを読み取ることができません。ログイン情報などが入ったCookieが盗まれるリスクを減らせます。

DevToolsの「Application」タブ→「Cookies」から、各Cookieの「HttpOnly」列を確認できます。

DevToolsのApplicationタブでCookie一覧のHttpOnly列を確認している画面

実際にyahoo.co.jpのCookie一覧を確認してみると、HttpOnly列にチェックが入っているCookieと入っていないCookieが混在していました。同じyahoo.co.jpのドメインでも、あるCookieにはHttpOnlyが付いていて、別のCookieには付いていない、という状態です。用途によってJavaScriptからの読み取りを許可する/許可しないを使い分けているようで、「全部まとめて対策されている」わけではなく、Cookieごとに設定を見ていく必要があるんだなと実感しました。

CSRFとは何か

CSRF(クロスサイトリクエストフォージェリ)は、ログイン中のユーザーに、そのユーザーが意図しないリクエストを送らせる攻撃です。例えば、悪意のあるサイトに仕込まれたリンクやフォームを踏んだだけで、ログイン中の別サービスに対して「パスワード変更」や「送金」のようなリクエストが勝手に送られてしまう、というのが典型的なパターンです。

ユーザー自身は「クリックした」だけのつもりでも、裏でログイン済みのサービスにリクエストが飛んでしまうのが怖いところです。

CSRFの対策、実際どうなってるか調べてみた

1. CSRFトークン

もしこれがなかったら…攻撃者が用意した外部サイトに、ログイン中のサービスへ送信される隠しフォームを仕込むだけで、パスワード変更や送金などのリクエストを勝手に送らせることができてしまいます。

フォームを送信する際に、サーバー側が発行したランダムな値(トークン)を一緒に送信させる方法です。悪意のあるサイトはこのトークンの値を知ることができないため、正規のフォームからのリクエストでなければ拒否されます。

実際にログインフォームなどのページでHTMLソースを確認すると、以下のようなhiddenな入力欄が埋め込まれていることがあります。

<input type="hidden" name="csrf_token" value="a1b2c3..." />

DevToolsの「Elements」タブでフォームを検索するか、「View Page Source」でページ全体のHTMLを確認すると見つけやすいです。今回はyahoo.co.jpではなく、ログインフォームを持つ別サービスとしてGitHubのサインインページで確認してみました。

GitHubのサインインページでElementsタブからauthenticity_tokenのhidden inputを確認している画面

authenticity_tokenという名前のhidden inputに、ランダムな値が入っているのが確認できます。この値がCSRFトークンで、フォーム送信時にサーバー側で検証されます。

2. SameSite Cookie属性

もしこれがなかったら…CSRFトークンを見破られなくても、外部サイトからのリクエストにログイン用Cookieがそのまま送られてしまい、サーバー側は「本人からの正規リクエスト」と誤認してしまいます。

CookieのSameSite属性は、そのCookieを異なるサイトからのリクエストに含めて送るかどうかを制御します。

  • Strict — 同一サイトからのリクエストにのみCookieを送る。最も厳しい設定
  • Lax — 一部の外部サイトからの遷移(通常のリンククリックなど)ではCookieを送るが、フォーム送信などでは送らない。多くのブラウザでのデフォルト値
  • None — サイトを問わずCookieを送る(通常はSecure属性とセットで使用)

先ほどの「Application」タブのCookie一覧で、「SameSite」列を確認すると、実際にどの設定が使われているかがわかります。

DevToolsのApplicationタブでCookie一覧のSameSite列を確認している画面

今回確認したCookieでは、「SameSite」列がNoneになっているものが複数見つかりました。ただしこれらはSecure属性も同時に付いており、HTTPS通信でのみ送信される設定になっていました。「None」だからといって無条件に危険というわけではなく、Secureとセットで運用されているかまで見る必要がある、という点も実際に確認して初めて気づけました。

3. フレームワークの標準対策

Rails、Laravel、Djangoなどの主要なフレームワークには、CSRFトークンの発行・検証機能が標準で組み込まれています。開発者が意識的に実装しなくても、フレームワークのデフォルト設定に従っていれば対策されている、というケースが多いようです。

調べる中で気づいた「よくある勘違い」

対策を調べる過程で、自分が漠然と思い込んでいたことが実は違っていた、というポイントがいくつかありました。

勘違い1:「HTTPSにしていればXSS/CSRFも防げる」

HTTPSは通信経路の暗号化(盗聴・改ざん防止)のための仕組みで、XSSやCSRFとは防ぐ対象がそもそも異なります。HTTPS化されているサイトでも、エスケープ処理やCSPが入っていなければXSSは成立しますし、CSRFトークンやSameSite属性がなければCSRFも成立します。「鍵付きの家」と「泥棒が入れないようにする対策」は別物、というイメージを持つとわかりやすいかもしれません。

勘違い2:「CSRFトークンさえ入れておけば安心」

CSRFトークンを検証していても、そもそものAPI設計に問題があると対策が意味をなさないケースがあります。例えば、本来は状態を変更する処理(データの更新・削除など)をGETリクエストで実装してしまっている場合です。

GETリクエストは、画像タグ(<img src=”…”>)のような形で外部サイトから簡単に発生させられてしまうため、CSRFトークンの検証が入っていないルート(GETリクエスト)から状態変更ができる設計自体が、対策の抜け穴になります。「状態を変更する処理は必ずPOST/PUT/DELETEなど、トークン検証が通る経路で行う」という設計そのものが対策の前提になっている、という点は見落としやすいポイントでした。

やってみてわかったこと

調べてみて感じたのは、普段使っているフレームワークやブラウザが、意識しないところで多くの対策をしてくれているということです。エスケープ処理やCSRFトークンの発行は、フレームワークのデフォルト機能に乗っているだけで、ある程度の対策が効いている場合が多いとわかりました。

一方で、CSPやSameSite属性のような設定は、意図的に設定しないと入らないものもあります。「デフォルトで安全」と思い込まず、実際にレスポンスヘッダーやCookieの設定を確認する習慣をつけることが大事だと感じました。

対策 何を防ぐか 確認する場所
エスケープ処理 XSS(スクリプト注入) フレームワークのデフォルト挙動
CSP XSS(不正なスクリプト実行) Response Headers
HttpOnly Cookie XSS経由のCookie盗取 Application → Cookies
CSRFトークン CSRF(不正なリクエスト送信) Elements(hidden input)
SameSite Cookie CSRF(外部サイトからのCookie送信) Application → Cookies

もっと詳しく知りたい人向け

今回はブラウザやフレームワークの挙動を実際に確認する、という進め方で調べましたが、より体系的に対策を知りたい場合はOWASP(Open Worldwide Application Security Project)のCheat Sheet Seriesが参考になります。

おわりに

CSRFとXSS、どちらも「名前は知っているけど詳しくは説明できない」という状態からのスタートでしたが、実際にDevToolsやレスポンスヘッダーを見に行くことで、普段自分が使っているツールやフレームワークがどこまで対策してくれているのかが見えてきました。

次にセキュリティの話が出たときは、まず自分たちのサービスのレスポンスヘッダーやCookieの設定を確認してみるところから始めてみようと思います。

この記事を気に入ったら

この記事を書いた人

だいせんせー

だいせんせー

コーヒーが好きですがカフェイン耐性が0。25卒新卒。

この人が書いた記事を見る >>