<style> .reveal { color: #2d469b; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 { padding-bottom: 15px; border-bottom: 2px solid #e7db6a; padding-left: 2rem; padding-right: 2rem; width: fit-content; color: #2d469b; } .reveal h2{ font-size: 70px; } .reveal a{ color: #7db098; } .reveal p { line-height: 1.6em; } .container{ background-color: #eff3f1; border-left: solid 30px #e7db6a; } .reveal .controls button.enabled{ color: #bc8695; } .reveal .controls button.enabled.highlight{ color: #ce407f; } .reveal { font-size: 32px; } .reveal .slides { text-align: left; width: 100%; } .reveal blockquote { font-size: 30px; padding: 0 1em; color: #777; border-left: 0.25em solid #ddd; box-shadow: none; width: 90%; margin-left: 3rem; font-style: normal; } .reveal code { color: #999; background-color: rgba( 0, 0, 0, .03 ); padding: 0; padding-top: .1em; padding-bottom: .1em; margin: 0; font-size: 85%; border-radius: 5px; white-space: pre-wrap; } .reveal table th, .reveal table td { text-align: left; padding: 0.6em 0.5em; } .reveal table tbody td{ font-size: 0.8em; } .reveal table th, .reveal table tbody td:first-child{ white-space: nowrap; } .reveal li { margin-bottom: 0.5em; } html[lang^="ja"] .reveal { font-family: "游ゴシック体", YuGothic, "游ゴシック", "Yu Gothic", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Roboto, Arial, "Hiragino Kaku Gothic Pro", "ヒラギノ角ゴ Pro W3", Osaka, Meiryo, "メイリオ", "MS Gothic", "MS ゴシック", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } .footnote-item { font-size: 2.5rem; color: gray; } .reveal .text-align-right{ text-align: right; margin-right: 3rem; } .reveal .text-align-center{ text-align: center; margin-right: 3rem; } .text-blue { color: #34b1eb; } .text-red { color: #eb346b; } .text-green { color: #327922; } .underline-blue { border-bottom: 2px solid #34b1eb; padding-bottom: 3px; } .underline-red { border-bottom: 2px solid #eb346b; padding-bottom: 3px; } .underline-green { border-bottom: 2px solid #327922; padding-bottom: 3px; } </style> ## websecjp講習会 ### 1/14 (火) --- ## 0. 導入 ---- ### Webにおける境界の必要性 世の中に数多くあるWebページの中には、悪意のある攻撃者によって作られたものも存在する 特定の人にしか見られないはずの Webサービス (q.trap.jp など) に対して、悪意のあるWebページがその中身を読み取ろうとしたり、サーバー上のデータを取得しようとしたりした時に、無制限にそれが許可されてしまっては困る → 異なるWebページ間のやりとりには、ある程度の制限が必要 ---- ![dounyu](https://i.imgur.com/fzlJAOm.png =800x) ---- ### 今日やること 1. **Origin**: ブラウザの根幹を支える境界 1. **Same-Origin Policy (SOP)**: やりとりの制限 1. **Cross-Origin Resource Sharing (CORS)**: SOPの緩和 1. **XSS**: SOPの弱点 1. **Post-XSSの世界を眺める**: XSS出現以降の対策と攻撃の発展 ---- ### 準備 Google Chromeを用意して、開発者ツールを開いておいてください (ウィンドウを開いて右クリック → 「検証」) --- ## 1. Origin ---- ### Originの定義 URI 中の (**スキーム**, **ホスト**, **ポート**) という 3 つの値の組をOriginと呼ぶ。 現代のWebでは、「ページ間でOriginが一致するかどうか」をページ間のやりとりの制限の基準としている。 - <span class="text-blue">スキーム</span>: `http`, `https`, ... - <span class="text-red">ホスト名</span>: `example.com`, `trap.jp`, ... - <span class="text-green">ポート</span>: `80`, `443`, `8080`, ... ただし、ポートが省略されている場合、スキームに対応したデフォルトのポート (http: 80, https: 443) が採用される。 ---- #### 例 |URI|Origin| |-|-| |<span class="underline-blue">http</span>://<span class="underline-red">example<span></span>.com</span>/hoge/fuga|(`http`, `example.com`, `80`)| |<span class="underline-blue">http</span>://<span class="underline-red">example<span></span>.com</span>:<span class="underline-green">8080</span>/hoge|(`http`, `example.com`, `8080`)| |<span class="underline-blue">https</span>://<span class="underline-red">q.trap<span></span>.jp</span>/channels/general|(`https`, `q.trap.jp`, `443`)| |<span class="underline-blue">https</span>://<span class="underline-red">anke-to.trap<span></span>.jp</span>/questionnaires/123|(`https`, `anke-to.trap.jp`, `443`)| ---- ### (参考) より厳密なOriginの定義 Originは、RFC 6454 \[4\] の Section 4 にて、任意のURIに対する、より厳密な定義がされています。 - [RFC 6454 - The Web Origin Concept](https://tools.ietf.org/html/rfc6454#section-4) - [日本語訳](https://triple-underscore.github.io/RFC6454-ja.html#section-4) --- ## 2. Same-Origin Policy (SOP) ---- ### 原則 - 2つのページの Origin が **一致** していれば、無制限で「やりとり」を **許可する** 。 - 2つのページの Origin が **異なって** いれば、「やりとり」を基本的に **禁止** する。 ---- ### 許可されるもの / されないもの ||Same-Origin|Cross-Origin| |-|-|-| |**書き込み**|制限しない<br>(許可される)|<span style="color: red">部分的に制限</span><br>(基本許可されるが、条件つきで禁止される)| |**埋め込み**|制限しない<br>(許可される)|制限しない (許可される)| |**読み込み**|制限しない<br>(許可される)|<span style="color: red">ほぼ制限</span><br>(基本的に禁止されるが、一部のリソースのみ許可される)| ---- ### 書き込みの部分的禁止 Cross-Origin間の書き込みは、 **単純リクエスト** によるもののみが許可され、他は禁止される 単純リクエストとは、以下のような条件全てを満たすリクエストのこと - メソッドが GET, POST, HEAD のいずれかである - リクエストヘッダには、特定のヘッダーしか含まれていない - Content-Type が、特定のもののいずれかである。 詳細な定義は [オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Examples_of_access_control_scenarios) の「単純リクエスト」の項を参照 ---- ### demo http://sop-cors.websecjp2019.trap.show:10000/sop.html 1. このページのOriginは? 2. 埋め込み、書き込み、読み込みを行っている、Cross-OriginなサイトのOriginは? (ソースコードは [こちら](https://git.trapti.tech/websecjp2019/SOP-CORS/src/branch/deploy/sop.html)) 3. 前スライドの表が成り立っていることを確認しよう --- ## 3. Cross-Origin Resource Sharing (CORS) ---- ### 背景 攻撃目的でなく、善良な開発者がCross-Originでやりとりを行いたい場合もある - 例: フロントエンドが `a.poyo.jp`, バックエンドが `b.poyo.jp` にあるWebアプリを作りたい - 例: フロントエンドの開発サーバーを `localhost:8080`で、バックエンドの開発サーバーを `localhost:1323` で動かしながら開発をしたい ---- **「OriginAのリソースの読み出しを、OriginBに限り許可する」** みたいなことがしたい → そこで **CORS** (Cross-Origin Resource Sharing / オリジン間リソース共有) の仕組みが整えられた ---- ### CORSとは **Cross-Origin なリソース共有** を、 **リソース保持者** が許可するための仕組み 具体的には、SOPが課す以下の2つの制限を緩和するための機能を提供する - 「Cross-Origin なリソース読み出しの禁止」の緩和 - 「Cross-Origin なリソース書き込みの部分的禁止」の緩和 ---- ### 不適切なCORSの利用が引き起こす問題 CORSは、SOPで守られているコンテンツに対して、その制限を緩和して弱める仕組み → **不適切な設定は大きなリスクを産む** 「とりあえず `Access-Control-Allow-Origin: *` を設定する」などといったおまじないに頼るのではなく、正しく仕組みと仕様を理解して設定するようにするべき ---- ### 「読み出しの禁止」の緩和 ---- #### 流れ CORSによるオリジン間リソース読み込みの例: ![cors1](https://i.imgur.com/1Sewjmh.png =800x) ---- #### 使用される主なレスポンスヘッダ [Access-Control-Allow-Origin](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Origin): ブラウザに対し、どのOriginに対して読み取りを許可するかを示す - `Access-Control-Allow-Origin: *`: 任意のOriginからの読み出しを許可 - `Access-Control-Allow-Origin: https://a.poyo.jp`: Origin (`https`, `a.poyo.jp`, `443`) からの読み出しのみ許可 ---- [Access-Control-Expose-Headers](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Expose-Headers): レスポンスに含まれるヘッダーのうち、クライアントがどのヘッダーを読み取れるかを示す。デフォルトで公開されるレスポンスヘッダー以外への読み取りを許可したい場合に使用する。 - `Access-Control-Expose-Headers: *`: 任意のヘッダーの読み取りを許可 - `Access-Control-Expose-Headers: Custom-Header-A, Custom-Header-B`: ヘッダー Custom-Header-A, Custom-Header-B の読み取りを許可 ---- ### 「書き込みの部分的禁止」の緩和 ---- #### SOPによる書き込みの部分的禁止 (再掲) 以下のような条件全てを満たすリクエスト (**単純リクエスト**) のみが許可され、他は禁止される - メソッドが GET, POST, HEAD のいずれかである - リクエストヘッダには、特定のヘッダーしか含まれていない - Content-Type が、特定のもののいずれかである。 詳細な定義は [オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Examples_of_access_control_scenarios) の「単純リクエスト」の項を参照 ---- #### 流れ OriginAが、OriginBに対して単純でないリクエストによるリソース書き込みを行いたい場合、 1. OriginBに対して、「どんなリクエストを送っても良いか」を尋ねるOPTIONSリクエストを送る (**プリフライトリクエスト**) 1. プリフライトリクエストに対するレスポンスを見て、送りたいリクエストが許可されるかどうかを判断する 1. 許可される場合のみ、実際に送りたいリクエストを送る ---- #### プリフライトリクエストで尋ねる内容 - リクエストを発生させた Origin からのリソース読み出しを許可してくれるか - リクエストの際に、 どんな **HTTP メソッド** を使用してよいか (例: PATCH, PUT, DELETE, ...) - リクエストの際に、どんな **ヘッダー** を使用してよいか ---- 単純でないリクエストによるリソース書き込みの例: ![cors2](https://i.imgur.com/49DGo5a.png =780x) (次スライドに続く) ---- 単純でないリクエストによるリソース書き込みの例 (続き): ![cors3](https://i.imgur.com/4YYjggm.png =780x) ---- #### 主なレスポンスヘッダー - [Access-Control-Allow-Origin](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Origin): ブラウザに対し、どのOriginに対してやりとりを許可するかを示す (単純リクエストの場合と同じ) - [Access-Control-Allow-Methods](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Methods): リクエストで使用していい HTTP メソッドを示す - [Access-Control-Allow-Headers](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Allow-Headers): リクエストに付与していいカスタムヘッダーを示す - [Access-Control-Max-Age](https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Access-Control-Max-Age): プリフライトリクエストに対して、このヘッダーと一緒に返却されたレスポンスの結果が、キャッシュされてよい時間 (単位: 秒) を示す ---- ### Cookieを使用したい場合 ユーザー機能のついたwebアプリなどでは、Cookieなどで認証情報をのせたリクエストを送る必要がある このようなリクエストに対するレスポンスは、認証情報なしで得られるレスポンスより 重要な情報を持っている場合が多い → Cookieを使用する場合の制限は、通常のCross-Originリクエストより厳しい ---- 1. Fetch API や XMLHttpRequest により発行される Cross-Origin リクエストには、原則 Cookie が付加されず、その都度コード中で明示する必要がある 2. Cookieを付加したリクエストを送る場合は、通常のCORSリクエストに更に条件が加わる ---- #### 1. Cookieなどの付加をコード中で明示<br>(リソースを利用したい側) [XHRHttpRequest](https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest)の場合 ```javascript= const xhr = new XMLHttpRequest() xhr.open(’GET’, url, true) xhr.withCredentials = true // 認証情報の使用を明示 xhr.send() ``` [Fetch API](https://developer.mozilla.org/ja/docs/Web/API/Fetch_API) の場合 ```javascript= fetch(url, { credentials: ’include’ }) ``` [Axios](https://github.com/axios/axios) の場合 ```javascript= axios.get(url, { withCredentials: true }) ``` ---- #### 2. CORSを利用する場合の条件 - 通常のCross-Originなリソースアクセスが許可される条件が揃っていること - レスポンスの `Access-Control-Allow-Origin` ヘッダーの中身が `*` ではなく、リクエスト中で指定したOriginを明示的に含んでいること - レスポンスが `Access-Control-Allow-Credentials: true` ヘッダーを含んでいること ---- ### CORSを利用したリクエストで有効にできるコンテンツ一覧 [オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#What_requests_use_CORS) より引用 > - XMLHttpRequest または Fetch API を呼び出す。 > - ウェブフォント > - WebGL テクスチャ > - drawImage() を使用してキャンバスに描画される画像やビデオフレーム > - 画像から生成する CSS シェイプ ---- ### demo http://sop-cors.websecjp2019.trap.show:10000/cors.html 1. Cross-OriginなPATCHリクエスト、GETリクエストが送れることを確認しよう 2. 開発者ツールの「ネットワーク」タブを開いて、どんなヘッダーが使われているかを確認しよう 3. プリフライトリクエストが送信されていることを確認しよう --- ## 4. Cross-Site Scripting (XSS) ---- ### SOPの弱点 SOPがページ間のリソースのやりとりに対して加える制約 (再掲) - 2つのページの Origin が **一致** していれば、無制限で「やりとり」を **許可する** 。 - 2つのページの Origin が **異なって** いれば、「やりとり」を基本的に **禁止** する。 → **同一Origin内のリソースのやりとりに対しては何の制約も加えない** ---- ### Cross-Site Scripting (XSS) 脆弱性 ある Origin 上に JavaScript を挿入できて、それをその Origin を持つ Web アプリケーション上で動作させることができる場合、そのサービスは **XSS脆弱性を持つ** という XSS脆弱性を持つサイトに JavaScript を挿入できた攻撃者は、その JavaScript が実行されるブラウザからアクセスできる **同一 Origin 内のデータ** を、 **自由に読み書きする** ことができる → この攻撃手法を **Cross-Site Scripting (XSS)** と呼ぶ ---- ### XSSによる攻撃の流れ 1. XSS脆弱性のあるサイトに対して、攻撃者がスクリプトを埋め込む 1. 被害者がそのサイトにアクセスすると、被害者のブラウザ上で、攻撃者が埋め込んだスクリプトが実行される 1. 埋め込まれたスクリプトが、被害者のブラウザ上で集めた情報を、攻撃者のサーバーに送信する ---- ![](https://i.imgur.com/fFO1Pmz.png) ---- #### HTML Injection 脆弱性 直接JavaScriptを埋め込める`<script>` タグ以外にも、攻撃者にとって挿入できて嬉しいタグは存在する 例: `<input>` タグなどが挿入できた場合、攻撃者のサーバーに対してPOSTを行う「ログインフォームもどき」を挿入し、それを被害者に見せ、IDとパスワードを送信させることができる このように、任意HTMLタグの挿入を許している脆弱性のことを **HTML Injection 脆弱性** と呼ぶ ---- ### demo (演習) [XSS Challenge (by y0n3uchy)](http://xss.shift-js.info/) Writeup: [XSS Challenge(セキュリティ・ミニキャンプ in 岡山 2018) Writeup - こんとろーるしーこんとろーるぶい](https://graneed.hatenablog.com/entry/2018/11/23/222842) --- ## 5. Post-XSSの世界を眺める XSSの出現 → 対策の発展 → 攻撃の発展 ---- ### 対策の発展 ---- #### フレームワーク側のXSS対策 モダンなWebフレームワーク / ライブラリの多くは、次のようなXSS対策を提供している - 基本は自動でエスケープ - 開発者がエスケープを意図的に無効化することもできるが、危険であることは喚起する - React: [`dangerouslySetInnerHTML`](https://ja.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml) - Vue: [`v-html`](https://jp.vuejs.org/v2/guide/syntax.html#%E7%94%9F%E3%81%AE-HTML) (eslint-plugin-vue の Recommended なルールとして、[no-v-html](https://vuejs.github.io/eslint-plugin-vue/rules/no-v-html.html) がある) ---- #### Content Security Policy (CSP) によるXSS対策 どれだけ対策をしても、XSS脆弱性やHTML injection 脆弱性を完全に根絶することは難しい → たとえinjectされてしまったとしても、それがeffectiveで無くなるように準備 (水際対策) をしておきたい 具体的には、 **ブラウザが「開発者が実行させたいスクリプト」と「攻撃者によって挿入されたスクリプト」を見分けられるように** すれば良い ---- #### [Content Security Policy (CSP)](https://developer.mozilla.org/ja/docs/Web/HTTP/CSP) JavaScript, CSS, 埋め込みコンテンツなどの **ソース・ターゲットを限定する** 機構 - `script-src 'https://poyo.jp'` 「JavaScriptはここ以外から読んだら駄目」 - `connect-src 'https://someapi.com'` 「ここ以外にXHRリクエストを送ったら駄目」 また、インラインスクリプト・インラインスタイルなどは全て無効とする ---- #### CSPの利用 CSPの設定には、Webページへのリクエストに対するレスポンスに、以下のように Content-Security-Policy HTTP ヘッダーをのせる ``` Content-Security-Policy: script-src self 'https://hoge.jp' 'https://fuga.jp'; connect-src 'https://someapi.com'; (その他適用したいCSPを表すディレクティブ) ``` 詳細は [コンテンツセキュリティポリシー (CSP) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/CSP) や [Introduction: Content Security Policy (CSP)](https://www.slideshare.net/lmt_swallow/introduction-content-security-policy-csp-14-91941801) などを参照 ---- #### [サブリソース完全性 (SRI)](https://developer.mozilla.org/ja/docs/Web/Security/Subresource_Integrity) JavaScript, CSS, 埋め込みコンテンツなどの **リソースが改竄されていないか** をブラウザが検証する機能 SRIを利用する場合、取得したいリソースのハッシュ値を予め計算しておき、 `<script src="(URL)" integrity="(計算したハッシュ値)">` の形で渡す ブラウザは、リソースを読み込む前に、想定されたハッシュ値と実際のスクリプトのハッシュ値が一致するかどうかを確認してくれる ---- CSPには、SRIの使用を強制させるディレクティブがある たとえば`require-sri-for script;` としておくと、JavaScriptの読み込みをSRI情報があり、かつチェックに通った場合のみを成功として扱うように制限する ---- ### 攻撃の発展 ---- #### Cross-Site Search (XS-Search),<br>Cross-Site Leaks (XS-Leaks) Scriptless Attacks (JavaScriptを使わずにできる攻撃) の一つ Cross-Originでも読める情報、読まれることが想定されていない情報などから、意味のある情報を抽出しようとする手法 1/22 の講習会で詳しくやります
{"metaMigratedAt":"2023-06-15T03:11:40.783Z","metaMigratedFrom":"YAML","title":"websecjp講習会 (SOP, CORS)","breaks":true,"contributors":"[{\"id\":\"0a630d25-e194-4b98-9eea-06fd2b04c6f5\",\"add\":59,\"del\":0},{\"id\":\"ae844454-e7b5-46cf-8b28-ab34834e9f8a\",\"add\":15548,\"del\":2238}]"}
    891 views