🔓

Webアプリのセキュリティをまとめていく!

2024/03/11に公開

Webアプリのセキュリティに関してまとめてみました!

徳丸さんの安全なWebアプリケーションの作り方に書かれているような基本的なことから、最近の話題も織り込んでいこうと思います。

誤っている内容や追記要望ある場合は、ぜひ更新したいためコメントお願いします🔏

概要

ざっと書いてる/書いてないを列挙します📝

書いてること

  • webアプリのセキュリティに関連する用語や歴史解説
    • 同一オリジンポリシー
    • サードパーティークッキー
    • クロスドメインアクセス
    • CORS
    • preflightなど
  • XSSやCSRFなど基本的な攻撃手法の原因と対策
  • 最近のセキュリティ関連の話に触れる
    • hashnodeの脆弱性
    • CSRF token辞めない?
  • 認証はJWTとSessionIdどっちで管理する?
  • cookieとlocalStorageどっちで管理する?
  • IDaaSのトークンはどこに保存しますか?

書いてないこと

アプリケーション層よりも低レイヤーのことはほぼ書いていないです。
クラウドインフラ関連にも触れていません。

理解に必要な脆弱性や歴史でボリュームが多くなってしまうため、関連する用語や概念への解説は最低限です。気になる箇所があったらlets deep dive!

本題の前にまずは最近の話題回収から...

サードパーティー クッキー🍪

サードパーティークッキーとは、アクセスしたWebサイトと異なるドメインが発行したCookieのこと。
それがChromeではデフォルトで段階的に制限されると下記でアナウンスされています。
早速ブロックされてて「段階的な1%にさっそく当たったん?」と謎の優越に浸りながらソースを探すと...下記がありました。

https://japan.googleblog.com/2023/12/chrome-cookie.html

サードパーティ Cookie へのアクセスをデフォルトで制限することで、ウェブサイト間トラッキングを防止するトラッキング保護機能のテストを 2024 年 1 月 4 日から、全世界の Chrome ユーザーの 1% に展開します。このテストは、2024 年後半に全てのユーザーのサードパーティ Cookie を段階的に廃止するというプライバシー サンドボックス の重要なマイルストーンです。

さて、このサードパーティークッキーの制限はセキュリティにどう影響するのかも踏まえて、以降で紹介するXSSやCSRFの項目で触れていきます。

Cookieを許可しますか?🚫

Cookieの規制には関連して別の話題もあります。
アクセスすると「Cookieを許可しますか?」のような確認が表示されることありますよね。

Cookie許可
引用 https://www.freestyle-entertainment.co.jp/blog/cookie_02/

EU一般データ保護規則(General Data Protection Regulation)により、Cookie等で個人情報を提供するかはユーザーの許可が必要になりました。
これは、日本のサイトでもEU圏で利用されるような場合は「Cookieを許可しますか?」のような確認が必要になるなど影響があります。

こちらもサードパーティークッキーの規制に近いものだと思うので、そこがセキュリティにどう影響するかも踏まえて進めて行きます。

それでは本編、はじまりはじまり。

Webセキュリティ

まずはじめに基本的なところから📝

クロスドメインアクセスとその歴史

まずクロスの前に同一を理解しましょう。
昔々「同一オリジンポリシー」というものがブラウザレベルで設定されました。

同一オリジンポリシーとは?

同一オリジンポリシーは同じオリジンから配信されたリソースにのみアクセスできる仕組みです。オリジンとは、プロトコル/ホスト名/ポート番号を指します。

昔は同一オリジンしかリソースアクセスできなかったのですが、サイトから別ドメインのAPIを利用する需要などはもちろんあり、不便ですねということでCORSが登場します。

CORS

CORSはサーバーがレスポンスヘッダーにAccess-Control-Allow-Origin: [許可するオリジン]などのCORS関連ヘッダーを含めることで、リソースを利用可能になります。


引用 https://dev.classmethod.jp/articles/cors-cross-origin-resource-sharing-cross-domain/

リクエストだけで攻撃できるなんて...
CORS設定してもデータ変わる系のリクエスト自体は飛んじゃう...うぅどうしたら...😢

preflight

CORS設定してもリクエストで攻撃できちゃうので、「リクエスト前にチェックしてやんぜ! by preflight」が登場します。(このためにpreflightが存在すると筆者は思っています。)

サーバーの情報に副作用を引き起こすことがあるメソッド(GET以外や特定のMIMEタイプのPOSTなど)を行うために、ブラウザがHTTPのOPTIONメソッドを用いてあらかじめ送信元のチェックをするリクエストのことです。

https://developer.mozilla.org/ja/docs/Glossary/Preflight_request


引用 https://developer.chrome.com/blog/private-network-access-preflight?hl=ja

これによりサーバー側へ副作用のあるリクエストを事前にチェックできます。

さて、クロスドメインアクセスに話題を戻します。

クロスドメインアクセス

あるサイト(ドメイン)が他のサイト(異なるドメイン)やAPIのリソースにアクセスできる機能を指します。別のドメイン/オリジンのリソースにアクセスする手段の例は下記の通りです。

  • iframe
  • script
  • css
  • img
  • formのaction
  • web apiへのリクエスト

これらは同一オリジンポリシーが免除される部分もありクロスドメインアクセスできますが、制限があります。

iframeは、コンテンツが異なるオリジンから読み込まれる場合、同一オリジンポリシーにより、そのコンテンツに対するjsからの直接アクセスは制限されます。
scriptタグは、取得できますがそのコードの中での他オリジンへの非同期通信などは制限されます。
imgタグも取得できますが画像データへjsでアクセスはできません。

この性質を念頭に次の節へ進んでいきましょう!

XSS

まずはじめに、とりあえずワードだけ知ってるXSS(Cross-Site Scripting)をご紹介します。
クロスドメインアクセスの次に紹介したので名前が誤解を与えてしまうかも。
クロスサイトは異なるオリジンとかではなく、攻撃に何かしらの対象サイトを利用して行うためにクロスサイトと名付けられています。


引用 https://www.shadan-kun.com/waf_websecurity/xss/

簡単にですが原因や対策を紹介します。

  • 主な発生箇所
    • 何かしらのHTML, Javascriptを生成している箇所
  • 攻撃種類
    • HTML埋め込み
    • 画像差し替え
    • cookieの読み出し
  • 原因
    • HTMLエスケープしてない
    • 動的に変更できるurlにjavascript:を許容している
      • <a href={javascript:alert(document.cookie)}>
  • 対策
    • HTMLエスケープする
    • HTTPレスポンスヘッダーに文字エンコーディングを指定する
      • Content-Type: text/html; charset=UTF-8
    • CookieにHttpOnlyなどをつけJSからcookieを読めなくする
    • urlをチェックする
    • リンク先urlをユーザーへチェックしてもらうような注意喚起する
  • 非推奨になった対策
    • X-XSS-ProtectionはブラウザでXSSをブロックする仕組みだったが非推奨になった

XSSは主に罠を埋め込まれたり、罠サイトに誘導されたりで発生すると思います。

CSP(Content Security Policy)

HTTPレスポンスのヘッダーとしてCSPがあります。
これはサイトで許可されるコンテンツの種類(画像、スクリプト、スタイルシートなど)と、その提供元(ドメイン、プロトコル、ポートなど)を明示的に指定できます。

strict-dynamicを使用することで、信頼されたスクリプトが動的に追加する他のスクリプトも許可することができます。

CSPを利用しホワイトリスト的に許可するコンテンツとドメインを指定し、XSSを緩和することができます。

CSRF

次にCSRF(Cross-Site Request Forgery)を取り上げます。

こちらも「Cross-Site」という名前から分かるように、攻撃に何かしらの対象サイトを利用して、その上で偽造されたリクエストを用いることで成立します。


引用 https://www.ipa.go.jp/security/vuln/websecurity/csrf.html

原因を見ていきます。
まずリクエストを偽造するためには偽造するためのsessionIdやトークンが必要となります。
これらを盗んでなりすます訳ですが、どのように盗むのでしょうか。

sessionIdはどう盗まれる?

XSSに対する脆弱性があると、スクリプトからCookieにアクセスされ盗まれる危険があります。
JSからCookieにアクセスできるとXSSによってJSが実行され盗まれてしまうんですね。
それを防ぐ方法として下記があります。

  • Secure
    • 設定されていると、そのクッキーはHTTPSを通じてのみ送信されます。
  • HttpOnly
    • が設定されているクッキーは、document.cookieを経由したアクセスやJavaScriptのようなクライアントサイドのスクリプトからはアクセスできなくなります。

これらを設定すればJSなどで盗まれなくはなります。
しかしながら「よし!じゃあ設定してCSRF対策終了じゃ!」とはなりません。
盗まなくても、成りすませるのです。

自動でセットされたCookieを利用される

set-cookieで設定したsessionIdなどは、リクエストヘッダなどにも自動で付与されます。
自動で付与されたものを利用されると、なりすまされて終わりです。

この対策がめっちゃ分かりにくいんですよね😔💥

Cookieの自動付与を変更する

SameSite 属性でCookieを付与するのか変更可能です。

SameSiteの値は下記をとります。

  • Strict:異なるドメインへのリクエストにCookieを付与しない
  • Lax:モダンブラウザのデフォルト値。異なるドメインへのPOST・画像リクエスト、XHR、iframe経由のリクエストにCookieを付与しない
  • None:以前のデフォルト値。異なるドメインへのリクエストであってもCookieを付与する

◉Strictの場合🚷
サイトのドメインとWebApiのドメインが同じでないといけませんが、これはCookieを付与しないためCSRFの対策になります。
一方でMozのサイトに下記のような記載があり、別サイトから該当サイトへ遷移した時などもCookieを付与してくれず、毎回ログアウトしたような状態になりUXが落ちます。

Strict では、ブラウザーは Cookie の元サイトからのリクエストに対してのみ Cookie を送ります。 Lax も同様ですが、ユーザーが Cookie の元サイトに移動したときに(たとえユーザーが異なる形のサイトから来たとしても)ブラウザーは Cookie を送信します。 例えば、外部サイトからリンクをたどった場合です。

◉Laxの場合🏄
こちらもサイトのドメインとWebApiのドメインが同じでないと使えませんが、StrictのようなデメリットもないですしCSRF対策になります。
しかし、GETなどで(誤った使い方で)重要な処理を行えるAPIがある場合、GETはCookieが付与されるためCSRFが成り立ちます。

また、デフォルト値としてついているLaxでPOSTを利用する場合、2分以内はCookieが付与されます。
https://developers-jp.googleblog.com/2020/02/2020-2-samesite-cookie.html

したがってLaxにしているから完全に対策できているわけではないんですね。

◉Noneの場合🆓
古いブラウザだとNoneがデフォルトのため、その時点で脆弱性があります。
また、サイトのドメインとWebApiのドメインが異なる場合は利用せざる負えません。
この対策をふかぼっていきます。

SameSite=None;にする場合のCSRF対策

基本的にサイトのドメインとWebApiのドメインが異なる場合などはNoneにせざる負えないと思います。

CORSやPriflightで対策する

クロスドメインアクセスを行うために制限を入れようとすると先ほど説明したCORSやPreflightがあります。しかし、これらは対策として弱いです。ブラウザ側の機能あっての機能なのでcurlなどブラウザを利用しないケースへの対策にはなりません。

Refererヘッダーの検証

Refererはリンク元のurlがセットされています。そのためリクエスト元が信用できるサイトか判別できそいうですが、公開非公開など変更される可能性があるため判別できない可能性があります。
省略や削除されてしまう状況下ではチェック不可となり、CSRFへの厳密な対策にはなりません。

CSRF tokenを利用する

重要なトランザクションの起こる際にtokenを発行し、リクエストに含める手段です。
これは有効なtokenが漏れない限りCSRF対策になります。

CSRF token以外は対策がないの?

Originヘッダーを利用した方法があるようです。
これはリクエストのOriginヘッダーとサーバー側のホスト情報を照合する方法です。

https://speakerdeck.com/hiro_y/update-your-knowledge-of-csrf-protection?slide=30

古いブラウザでもOriginヘッダーは利用できるため互換性も確保できます。
https://github.com/sveltejs/kit/issues/72
https://kit.svelte.jp/docs/configuration#csrf

許可するOriginをホワイトリスト形式で検証する方法だと思います。
Originがない場合は許可しないケースだと、デバッグとかやりにくくなる可能性はありそうです。

サードパーティークッキーのCSRFへの影響

サードパーティークッキーがデフォルトで制限されることにより、なりすます先のサイト(攻撃するサイト)のcookie付与が自動で付与されないはずです。
したがって抑制にはなるはずですが、この制限はユーザー自身で設定変更可能なため対策にはなりません。

CSRFおまけ

samesite:none以外にもcookieが漏れるシチュエーションはあります。
catnose99さんが紹介していたのでリンク貼っておきます。

Cookieのdomain属性の値がexample.comではなく.example.comとなっている場合、そのCookieはサブドメインのホストにも送られてしまいます。

https://zenn.dev/catnose99/articles/152168e3e5553b

ひとこと

クロスドメインでCookieを使うのはむずい

認証

これまでCookie周りやSessionIdを利用した偽装などの脆弱性に触れてきました。
そうなるとCookieもSessionIdも使わない方法とればいいんじゃね?となります。

そういえば他のストレージとかインメモリとか、JWTもあったな...といろんなワードが..🤔
そこら辺を見て行きましょう。

深堀るに当たり比較対象として、sessionIdはJWT、cookieはlocalStorageと比較してみます。

認証はsessionIdかjwtか

結論を先に申し上げると、筆者的には「SessionIdを保持する認証かJWTなどのトークンでの認証かは状況によって異なる」です😶‍🌫️

まず比較するための観点を明確にしておきます。
ステートフルかステートレスかです🗽

サーバー側で状態を保持しておくようなsessionIdで認証するものはスレートフル。
JWTなどでサーバー側で状態を持たないようなものはステートレスとしています。

ステートフルな認証方法は、サーバー側で認証を切れます(ログアウトさせられる)✨
一方でステートフルは有効期限をきめ、その期間内は基本的に有効なままです🎫

ステートフルな認証はサーバーで状態を保つため、セッションDBがスケールのボトルネックになりやすくスケールしにくい傾向があります📉

パスワード変更後の即時ログアウトが必要かどうかなど、ユースケースによって選択するのがいいと思います!

cookieかlocalStorageか

こちらも筆者の考えは、どちらが良いとかはない(どちらにもリスクはあり別々の対策が必要)です。
クライアントとサーバーが同一ドメインならcookieにて管理するのが無難だとは思います。

XSSはいかなる場合も対策が必要だが、localStorageはXSSで取得できてしまうため注意が必要です。cookieでhttpOnlyがない場合も同様🚧

おまけ(IDaaSのトークンはどこに保存しますか?)

下記の記事、面白かったのでご紹介。
IDaaSのトークンはIDaaS側のドメインで表示されるログイン画面でログインしtokenを受け取るため、サードパーティークッキーになり取扱難しくなると思ってしまいました。

しかしながら下記の素晴らしいslideでもあるとおり、IDaaS側のファーストパーティークッキーとして保持しリクエスト時にサイトへ渡してくれるようで、結果サードパーティークッキーにはならない仕組みとのこと。

サードパーティクッキーが使えないとAuth0で毎回ログインしなきゃダメと思ってたけど誤解だった件

最後に

長文読んでいただきありがとうございました!
一気に書いたのて疲れたので、今後も徐々に追記して行きたいと思います📝
誤りや追記して欲しい内容あれば、ぜひぜひコメントお願い致します🥺

Discussion