Cache Probingは、あるリソースがブラウザによってキャッシュされているかどうかを検知する手法です。そのコンセプトは、初期のWeb1から知られており、当初はタイミングの差異を検知することに基づいていました。
ユーザがWebサイトに訪れると、画像、スクリプト、そしてHTMLコンテンツなどの様々なリソースが取得され、それらは(特定の条件下で)ブラウザによってキャッシュされます。この最適化により、ブラウザはこれらのリソースを再度要求することなくディスクから提供するため、その後のナビゲーションが高速化されます。もし攻撃者がどのリソースがキャッシュされているかを検知できれば、この情報を基に、ユーザが特定のページにアクセスしたことがあるかどうかをリークできます。
Cache Probingのバリエーションとして、Error Eventsを悪用することにより、より正確でインパクトのある攻撃を実行することができます。
攻撃の原理 # 攻撃者は、あるユーザーがあるSNSを訪問したかどうかを知りたいと考えています。
そのSNSにアクセスすると、いくつかのサブリソースがキャッシュされます。 ユーザが、SNSが通常取得するリソースを取得する攻撃者のページを訪問する。 Network Timing XS-Leakの手法を用いて、攻撃者のページはキャッシュからの応答(つまりステップ1が起こった)とネットワークからの応答(つまりステップ1が起こらなかった)の違いを検出することができます。(応答がキャッシュからの場合、遅延は顕著に短くなります。) Error EventsによるCache Probing # Error Events2を利用したキャッシュプロービングは、より正確な攻撃を可能にします。このアプローチでは、時間の計測に頼らず、Error Eventsといくつかのサーバサイドの挙動を活用して、あるリソースがキャッシュされたかどうかを検出します。この攻撃は、以下のステップを必要とします。
ブラウザキャッシュからのリソースを無効化する。このステップは、攻撃が別の訪問で以前にキャッシュされたリソースを考慮しないことを確認するために必要です。 ユーザーの状態によって異なる項目がキャッシュされるようなリクエストを実行する。例えば、ユーザーがログインしている場合にのみ、特定の画像を含むページをロードする。このリクエストは、<link rel=prerender...で対象のウェブサイトに移動したり、iframeでウェブサイトを埋め込んだり、window.openで新しいウィンドウを開くことで発生させることができる。 サーバが拒否するようなリクエストを引き起こす。例えば、長大なRefererヘッダを含んでいて、サーバーがリクエストを拒否するような場合です。もし、ステップ2でリソースがキャッシュされていれば、このリクエストはエラーイベントを発生させることなく、成功します。 キャッシュの無効化 # キャッシュからのリソースを無効にするには、攻撃者はそのサブリソースを取得する際にサーバーがエラーを返すようにする必要があります。これを実現するには、いくつかの方法があります。
ブラウザによってリクエストが開始され、新しいコンテンツを受け取る前にAbortController.abort()で中止された cache:'reload'オプション付きのフェッチリクエスト 長大なRefererヘッダと 'cache':'reload'を持つリクエスト。ブラウザはこれを防ぐためにリファラの長さに上限を設けているので、これはうまくいかないかもしれません。 POSTリクエストに fetch no-cors を指定した場合。エラーが返されない場合でも、ブラウザがキャッシュを無効化することがあります。 Content-Type、Accept、Accept-Languageなど、サーバーを失敗させる可能性のあるリクエストヘッダ。(よりアプリケーションに依存します) その他のリクエストプロパティ これらの方法のいくつかは、しばしばブラウザのバグとみなされていそうです。(例: this bug).
Origin ReflectionによるCORS error # Origin Reflectionは、グローバルにアクセス可能なリソースに、リクエストを初期化したオリジンを反映したAccess-Control-Allow-Origin(ACAO)ヘッダを付与する挙動のことです。これは、CORSの設定ミス3と考えることができ、ブラウザのキャッシュにリソースが存在するかどうかを検知するのに使用できます。
info
例えば、Flask frameworkにおいて、origin refrectionはデフォルトの動作になっています。 もし server.com にホストされているリソースが target.com からリクエストされた場合、オリジンはレスポンスヘッダに次のように反映されるでしょう。Access-Control-Allow-Origin: target.com といった具合です。リソースがキャッシュされている場合、この情報はリソースと一緒にブラウザのキャッシュに保存されます。これにより、もし attacker.com が同じリソースを取得しようとした場合、2つの可能性があります。
リソースがキャッシュにない場合:リソースは Access-Control-Allow-Origin: attacker.com ヘッダーとともにフェッチされ、保存される可能性があります。 リソースがすでにキャッシュにある:フェッチ試行はキャッシュからリソースをフェッチしようとするが、要求しているオリジンと ACAO ヘッダーの値の不一致によりCORSエラーが起こる。(target.comが期待されているが、実際に提供されたのはattacker.com 。)以下に、この脆弱性を悪用して被害者のブラウザのキャッシュ状態を推測するコード例を示します。 // この関数は、単にURLを受け取り、CORS モードでfetchします。 // fetchでエラーが発生した場合、attacker.
...
一部のHTML要素は、クロスオリジンのページにデータの一部をリークさせるために使用される可能性があります。 たとえば、以下のようなメディアリソースは、サイズ、期間、種類に関する情報をリークさせる可能性があります。
HTMLMediaElementは、メディアのdurationとbufferedの時間をリークします。 HTMLVideoElement はvideoHeightとvideoWidthをリークします。一部のブラウザでは、webkitVideoDecodedByteCount、 webkitAudioDecodedByteCount、webkitDecodedFrameCountも含む可能性があります getVideoPlaybackQuality()はtotalVideoFramesをリークします。 [HTMLImageElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement)はheightとwidthをリークしますが、画像が無効な場合には、それらは0となり、image.decode()は拒否されます。 メディアタイプの固有のプロパティによって、メディアタイプを区別することができます。 たとえば、<video>の場合はvideoWidth、<audio>の場合はdurationといった具合です。 以下のコードは、リソースの種類を返すサンプルコードを示しています。
async function getType(url) { // リソースがaudioもしくはvideoかどうかを検知 let media = document.createElement("video"); media.src = url; await new Promise(r=>setTimeout(r,50)); if (media.videoWidth) { return "video"; } else if (media.duration) { return "audio" } // リソースがimageかどうかを検知 let image = new Image(); image.src = url; await new Promise(r=>setTimeout(r,50)); if (image.width) return "image"; } CORBの悪用 # CORB は、間違ったコンテンツタイプが使用された場合にレスポンスを空にするChromeの機能です。 これは、コンテンツタイプが間違っている場合、キャッシュされないことを意味します。 ifCached 関数についてはCache Probingの記事にて確認できます。
async function isType(url, type = "script") { let error = false; // urlをパージする await ifCached(url, true); // リソースの読み込みを試行 let e = document.
...
October 1, 2020
ブラウザ上でのJavaScriptの実行時間を測定することで、攻撃者は特定のイベントがいつ発生したか、ある操作にどれくらいの時間がかかったかといった情報を得ることができます。
イベントループのタイミング # JavaScriptの並行処理モデルは、single-threaded event loopに基づいており、これは一度に一つのタスクしか実行できないことを意味します。 例えば、ある時間のかかるタスクがイベントループをブロックした場合、UIスレッドが枯渇した結果として、ユーザーはページがフリーズしたように感じることがあります。 他のタスクはブロックされたタスクが終了するまで待たなければなりません。 各ブラウザは異なるプロセスモデルを実装しているので、ウェブサイトによっては、その関係によって異なるスレッド(およびイベントループ)で実行されることがあります。
このモデルを悪用して、クロスオリジンページから秘密を盗みだす手法があります。
イベントプールで次に実行されるまでの時間を測定することで、異なるオリジンのコードが実行されるまでの時間を推測する。攻撃者は固定されたプロパティを持つイベントをイベントループ12に送り続け、プールが空になると最終的にディスパッチされます。他のオリジンは同じプールにイベントをディスパッチし、ここで攻撃者はそのタスクの一つで遅延が発生したかどうかを検出することで、時間差を推測します。 攻撃者が制御する文字列によって前記の機密情報が比較される場合、クロスオリジンページから機密情報を盗む。このリークは、1文字ずつの文字列比較2のイベントループで時間差を比較した結果です(前出の手法を使用)。process isolationを行わないブラウザでは、異なるオリジン間のクロスウィンドウ通信が同じスレッドで実行され、同じイベントループを共有します。 important
process isolationの仕組みがあるブラウザでは、後者の攻撃の可能性はすでにありません。このような仕組みは、現在Chromiumベースのブラウザでは Site Isolationのみで、Firefox にはProject Fission という名前で間もなく導入される予定です。 Busy Event Loop # 別の手法として、スレッドのイベントループをブロックし、イベントループが再び利用可能になるまでの時間を計測する方法があります。 この手法の主な利点の1つは、攻撃者のオリジンが他のオリジンの実行に影響を与えることができるため、Site Isolationを回避できることです。この攻撃は次のように動作します。
対象のウェブサイトをwindow.openで別ウィンドウに表示するか、iframe内に表示する。(Framing Protectionsが設定されていない場合) 長い計算が始まるのを待つ。 Framing Protectionsに関係なく、同じサイトのページをiframe内に読み込む 攻撃者は、(手順3の)iframeがonloadイベントをトリガーするのにかかった時間を計ることで、ターゲットのウェブサイトが実行された時間を検出できます。(Network Timing of step 3 should be minimal) 両方のナビゲーションは同じコンテキストで発生し、それらは同じサイトであるため、同じスレッドで実行され、同じイベントループを共有します。(それらは互いにブロックすることができます。)
// 新しいウィンドウを開いて、example.comのイベントループを // ウィンドウがブロックする時間を測定する window.open('https://example.org/expensive'); // TODO: タイムアウトなどでコストの掛かるウィンドウがロードされるのを待ちます。 var ifr = document.createElement('iframe'); ifr.src = "https://example.org"; document.body.appendChild(ifr); // 初期時間の測定 var start = performance.now(); ifr.onload = () => { // iframeが読み込まれたら、時間差を計算する var time = performance.
...
October 1, 2020
ハイブリッド・タイミング攻撃では、攻撃者は最終的なタイミング測定に影響を与える一連の要素の合計を測定することができます。これらの要素には以下のものがあります。
ネットワークの遅延 ドキュメントのパース サブリソースの検索と処理 コード実行 アプリケーションによって値が異なる要素もあります。つまり、Network Timing はバックエンドの処理が多いページでより重要かもしれませんし、一方 Execution Timing はブラウザ内でデータを処理し表示するアプリケーションでより重要かもしれないのです。攻撃者は、より正確な測定値を得るために、これらの要因のいくつかを排除することも可能です。例えば、攻撃者はページを iframe として埋め込むことによってすべてのサブリソースを事前にロードし(ブラウザにサブリソースをキャッシュさせる)、その後、それらのサブリソースの取得によって生じる遅延を除外した2度目の計測を行えます。
Frameタイミング攻撃(Hybrid) # ページがFraming Protectionsを設定していない場合、攻撃者はすべての要素を考慮したハイブリッドな計測結果を取得できます。この攻撃はNetwork-based Attackと似ていますが、リソースが取得されると、ブラウザによってページがレンダリングされて実行されます(サブリソースの取得と JavaScript の実行が行われる)。このシナリオでは、onloadイベントは(サブリソースとスクリプトの実行を含めて)ページが完全にロードされたときだけトリガーされます。
var iframe = document.createElement('iframe'); // 配送先のWebサイトのURLを設定する iframe.src = "https://example.org"; document.body.appendChild(iframe); // リクエストが初期化されるまでの時間を計測する var start = performance.now(); iframe.onload = () => { // iframeが読み込まれたら、時間差を計算する var time = performance.now() - start; console.log("The iframe and subresources took %d ms to load.", time) } 対策 # Attack Alternative SameSite Cookies (Lax) COOP Framing Protections Isolation Policies Frame Timing (Hybrid) ✔️ ❌ ✔️ FIP
October 1, 2020
id属性はHTML要素を識別するために広く利用されています。残念ながらクロスオリジンのウェブサイトでfocusイベントとURLフラグメントを利用することによって、ページのどこにid属性が与えられているかを決定することができます。例えば、https://example.com/foo#barが読み込まれると、ブラウザはid="bar"が与えられた要素までスクロールしようとします。これはクロスオリジンのサイトで定義したiframeでhttps://example.com/foo#barを読み込むことによって検出することができます。もし、 id="bar" を持つ要素がある場合は、 focus イベントが発生します。 focus イベントもまた同じ目的で使用することができます1。
いくつかのウェブアプリケーションはfocusable要素にid属性を設定しており、ユーザ情報の開示につながります。これらのid属性には、ユーザに直接関連する機密情報やユーザの状態(アカウントの状態)に関連する情報を含めることができます。
コード # 以下のコードは別のサイトからID属性を検出する例を示しています:
// onblurイベントをリッスン onblur = () => { alert('Focus was lost, so there is a focusable element with the specified ID'); } var ifr = document.createElement('iframe'); // ページに id="x" のフォーカス可能な要素がある場合、フォーカスを得ることができます。 // 例: <input id="x" value="test" /> ifr.src = 'https://example.org/#x'; document.body.appendChild(ifr); info
上記の手法はFireFoxではうまく動作しない可能性があります。 攻撃シナリオ # id属性を利用した攻撃は以下のような物があります。
とある銀行がモバイルデバイスのセッションを認証するために短い数字のワンタイムPin(OTP)を生成することを許可しています。この銀行のページはクライアントにPINコードを表示するためのbutton要素のidにOTPコードそのものを使用していました。この挙動を悪用し、すべてのオプションをブルートフォースすることでOTPコードを窃取し、ユーザアカウントを侵害することができます。 とあるウェブアプリケーションが、プレミアムアカウントのステータスを持つユーザや、特定の性別のユーザである場合、あらかじめ定義されたidとHTML要素の組み合わせを利用します。攻撃者は、被害者のページに特定のidがあるかを検出し、被害者のアカウント情報を漏えいさせることができます。 対策 # Document Policies SameSite Cookies (Lax) COOP Framing Protections Isolation Policies ✔️ ✔️ ✔️ ❌ FIP 参考文献 # Leaking IDs using focus, link ↩︎
October 1, 2020
アプリケーションは、他のオリジンと情報を共有するために、しばしば postMessage broadcasts を使用します。 postMessage を使うと、2種類の XS-Leaks につながる可能性があります。
信頼できない発信元と機密性の高いメッセージを共有すること
postMessage API は targetOrigin パラメータをサポートしており、これを使用してメッセージを受信できるオリジンを制限することができます。メッセージに機密性の高いデータが含まれている場合、このパラメータを使用することが重要です。 変化するコンテンツやブロードキャストの存在に基づいた情報のリーク
他の XS-Leak のテクニックと同様に、これはオラクルを形成するために使われる可能性があります。例えば、特定のユーザ名を持つユーザが存在する場合にのみ、アプリケーションが「Page Loaded」という postMessage ブロードキャストを送信すると、これを利用して情報をリークすることができます。 対策 # この XS-Leak は、postMessage のブロードキャスト送信の目的に深く依存するため、明確な解決策はありません。 アプリケーションは、postMessage の通信を既知の送信元グループに制限する必要があります。 これが不可能な場合、攻撃者が通信間の違いに基づいて情報を推論するのを防ぐために、ユーザの状態に関係なく通信が一貫して同じ動作をする必要があります。
References #
CSSインジェクション # warning
ここで紹介される一連のXS-Leaksは対象ページでのCSSインジェクションを必要とします。 CSSインジェクションの様々なベクトルの中で、最もよく見られるのは、CSSセレクタの悪用です。これらは、特定のHTML要素にマッチし、選択するための表現として使用することができます。例えば、セレクタ input[value^="a"] は、input タグの値が文字 “a” で始まっている場合にマッチングされます。したがって、ある CSS セレクタが表現にマッチするかどうかを検出するため、攻撃者は background や @import などのプロパティを使用して、自身の管理するウェブサイトへのコールバックをトリガーすることができます。1 2 このマッチングプロセスは簡単にブルートフォースされ、文字列全体に拡張できます。
文字の並びに独自の表記がある場合、フォントの合字を悪用して、ページに含まれるJavaScriptなどがリークされる可能性があります。
* { display: block; }のようなスタイルを適用すると、styleやscriptなどの通常は隠されているHTMLタグも、テキストとして表示されることがあります。したがって、それらの内容もリークされる可能性があります。
対策 # 攻撃者がコントロールするコンテンツを独立したドキュメントに置きます。これは srcdoc属性を持つiframeを使って行うことができます。オプションで、コンテンツを独自のオリジンに分離するためのサンドボックス属性を含めることができます。 CSSインライナーを使って、グローバルなスタイルが変換されるようにします。 SameSite Cookies (Lax) COOP Framing Protections Isolation Policies ❌ ❌ ❌ ❌ References # CSS Injection Primitives, link ↩︎
HTTPLeaks, link ↩︎
October 1, 2020
network timingを測定する方法の一つとして、ブラウザのソケットプールを悪用する方法があります。 ブラウザはサーバと通信するために、ソケットを利用します。 ハードウェアやその上で動作するOSのリソースには限りがあるため、ブラウザにも制限をかけざるを得ません。
この制限の存在を悪用するために、攻撃者は下記のようなことができます。
ブラウザの制限を確認する。 単に接続をハングアップさせる \(255\) のリクエストを別々のホストに実行して、長時間 \(255\) のソケットをブロックする。 ターゲットページに対するリクエストを実行して、 \(256^{番目}\) のソケットを利用する。 他のホストへの \(257^{番目}\) のリクエストを実行します。(step2、3で)すべてのソケットが使用されているので、このリクエストは、プールが利用可能なソケットを受け取るまで待機する必要があります。この待ち時間は、ターゲットページに属する \(256^{番目}\) のソケットのネットワークタイミングを、攻撃者に提供します。これが動作するのは、ステップ2の \(255\) 個のソケットがまだブロックされているので、ステップ3のソケットの解放によってプールが利用可能なソケットを受信した場合です。 \(256^{番目}\) のソケットを解放する時間は、リクエストを完了するのにかかった時間と直結しています。 対策 # SameSite Cookies (Lax) COOP Framing Protections Isolation Policies ❌ ❌ ❌ ❌ info
partitioned cachesと同様に、リソースの"site/originごとの分割"の原理をソケットプールに拡張することを、いくつかのブラウザが検討しています。 参考文献 #