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

Brave Browserの脆弱性を見つけた話(iOS編)

Brave Browserの脆弱性を見つけた話(iOS編)

Shibuya.XSS techtalk #12の発表資料です。

MUNEAKI NISHIMURA

July 25, 2023
Tweet

More Decks by MUNEAKI NISHIMURA

Other Decks in Technology

Transcript

  1. Brave Browserの脆弱性を⾒つけた話(iOS編)
    Shibuya.XSS techtalk #12
    Muneaki Nishimura (nishimunea)

    View full-size slide

  2. ⾃⼰紹介
    ⻄村 宗晃(にしむねあ)
    ブラウザの脆弱性を探すのが⼤好き
    好きなブラウザはFirefox
    https://www.slideshare.net/codeblue_jp/firefox-by
    https://files.speakerdeck.com/presentations/3cf321f5ee4d4aaba9f18305ee2a461c/SWIFT_Code_for_Mozilla_Bank__nishimunea_.pdf 1

    View full-size slide

  3. Braveとは︖
    JavaScriptの⽣みの親 Brendan Eich⽒が
    開発したオープンソースのWebブラウザ
    広告ブロック、暗号資産連携、Torを⽤いた
    プライベートブラウズモード等の野⼼的な
    機能を多数搭載
    2

    View full-size slide

  4. Braveの脆弱性を探し始めた経緯 1/3
    僕もブラウザの脆弱性を
    探してみたいのですが…
    最初は簡単そうなところ
    が良いね。HackerOneに
    Braveってブラウザが
    あったけどどうかな︖
    インターン⽣ わし
    きっかけは当時、弊社に来ていたインターン⽣との会話
    3

    View full-size slide

  5. Braveの脆弱性を探し始めた経緯 2/3
    思い付きで他⼈に勧めるのも良くないので、試しに⾃分でも探してみたところ…
    ちょっと⾃分でも
    探してみるか…
    わし
    4

    View full-size slide

  6. Braveの脆弱性を探し始めた経緯 3/3
    気付いたら、誰よりもBraveの脆弱性探してるマンになっていた(発表時点)
    https://hackerone.com/brave/thanks
    わし
    5

    View full-size slide

  7. 本⽇は⾒つけた脆弱性を3つ紹介
    6
    XSS関連

    View full-size slide

  8. リーダーモードに存在したXSS

    View full-size slide

  9. リーダーモードとは︖
    Webページから余計なものを削除して、本⽂を読みやすくしてくれる機能
    押す
    8

    View full-size slide

  10. Braveアプリ内の
    HTMLテンプレート
    リーダーモードの仕組み 1/2
    元ページからタイトル、クレジット、本⽂を抽出し、Braveがアプリ内に保持する
    HTMLテンプレートに埋め込む
    タイトル
    クレジット
    本⽂
    9

    View full-size slide

  11. Braveアプリ内の
    HTMLテンプレートに
    埋め込んだ結果
    リーダーモードの仕組み 2/2
    埋め込んだ結果をアプリ内のローカルHTTPサーバから配信して表⽰
    表⽰時にはCSP(Content Security Policy)を付与することによりXSSを防⽌
    XSS防⽌のためCSPを付与
    script-src: 'nonce-XXXXX'
    10

    View full-size slide

  12. ⾒つけた脆弱性①︓CSPのnonce制限を迂回したXSS
    元ページからクレジットを埋め込む箇所にHTMLインジェクションがあった
    しかしCSP(script-srcのnonce制限)が効いているためXSSは困難…
    著者名にタグを挿⼊
    11
    https://hackerone.com/reports/1436142
    タグが効いてる

    View full-size slide

  13. CSPのnonceはどのように埋め込まれるのか︖
    HTMLテンプレート内に埋め込まれた特殊⽂字列をNative側(Swift)で置換する
    12
    https://github.com/brave/brave-ios/pull/4209/files#diff-eaeef15a290e9e5e9bcaae784f18d874f8c932dfa3de416a5820eccd6b2d8cfbR54

    %READER-CREDITS%
    <br/>document.getElementById("reader-title").textContent = "%READER-TITLE%";<br/>
    %READER-CONTENT%
    HTMLテンプレート
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)
    テンプレート内の特殊⽂字列を置換するSwiftコード

    View full-size slide

  14. HTMLテンプレート置換処理の実際の動き 1/8
    はじめに元のページのタイトルが埋め込まれる
    13
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)
    テンプレート内の特殊⽂字列を置換するSwiftコード

    %READER-CREDITS%
    <br/>document.getElementById("reader-title").textContent = "%READER-TITLE%";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  15. HTMLテンプレート置換処理の実際の動き 2/8
    はじめに元のページのタイトルが埋め込まれる
    14

    %READER-CREDITS%
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)
    テンプレート内の特殊⽂字列を置換するSwiftコード

    View full-size slide

  16. HTMLテンプレート置換処理の実際の動き 3/8
    次に元のページのクレジットが埋め込まれる
    15
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    %READER-CREDITS%
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  17. HTMLテンプレート置換処理の実際の動き 4/8
    次に元のページのクレジットが埋め込まれる
    16
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    Credit
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  18. HTMLテンプレート置換処理の実際の動き 5/8
    続いて元のページの本⽂が埋め込まれて…
    17
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    Credit
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  19. HTMLテンプレート置換処理の実際の動き 6/8
    続いて元のページの本⽂が埋め込まれて…
    18
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    Credit
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  20. HTMLテンプレート置換処理の実際の動き 7/8
    最後にランダムなnonceの値が埋め込まれる
    19
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    Credit
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  21. HTMLテンプレート置換処理の実際の動き 8/8
    最後にランダムなnonceの値が埋め込まれる
    20
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    Credit
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  22. Braveアプリ内の
    HTMLテンプレート
    お気づきになりましたか…
    scriptタグのnonceに「%READER-TITLE-NONCE%」という⽂字列を指定すると、
    Braveが正しいnonceに置き換えてくれる
    %READER-TITLE-NONCE%
    タイトル
    本⽂
    21
    https://hackerone.com/reports/1436142

    View full-size slide

  23. HTMLテンプレート置換処理の実際の動き(再掲) 1/8
    はじめに元のページのタイトルが埋め込まれる
    22
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)
    テンプレート内の特殊⽂字列を置換するSwiftコード

    %READER-CREDITS%
    <br/>document.getElementById("reader-title").textContent = "%READER-TITLE%";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  24. HTMLテンプレート置換処理の実際の動き(再掲) 2/8
    はじめに元のページのタイトルが埋め込まれる
    23

    %READER-CREDITS%
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)
    テンプレート内の特殊⽂字列を置換するSwiftコード

    View full-size slide

  25. HTMLテンプレート置換処理の実際の動き(再掲) 3/8
    次に元のページのクレジットが埋め込まれる
    24
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    %READER-CREDITS%
    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  26. HTMLテンプレート置換処理の実際の動き(再掲) 4/8
    次に元のページのクレジットが埋め込まれる
    25
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)



    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  27. HTMLテンプレート置換処理の実際の動き(再掲) 5/8
    続いて元のページの本⽂が埋め込まれて…
    26
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)



    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    %READER-CONTENT%
    HTMLテンプレート

    View full-size slide

  28. HTMLテンプレート置換処理の実際の動き(再掲) 6/8
    続いて元のページの本⽂が埋め込まれて…
    27
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)



    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  29. HTMLテンプレート置換処理の実際の動き(再掲) 7/8
    最後にランダムなnonceの値が埋め込まれる
    28
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    %READER-TITLE-NONCE%

    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  30. HTMLテンプレート置換処理の実際の動き(再掲) 8/8
    最後にランダムなnonceの値が埋め込まれる
    29
    テンプレート内の特殊⽂字列を置換するSwiftコード
    .replacingOccurrences(of: "%READER-TITLE%", with: readabilityResult.title)
    .replacingOccurrences(of: "%READER-CREDITS%", with: readabilityResult.credits)
    .replacingOccurrences(of: "%READER-CONTENT%", with: readabilityResult.content)
    .replacingOccurrences(of: "%READER-TITLE-NONCE%", with: titleNonce)

    80841642004944135718143322189292

    <br/>document.getElementById(“reader-title”).textContent = "Title";<br/>
    Contents
    HTMLテンプレート

    View full-size slide

  31. 様々なUniversal XSS

    View full-size slide

  32. Universal XSS(UXSS)とは︖
    対象のサイトにXSSの脆弱性が無くてもXSSで攻撃できてしまうブラウザの脆弱性
    SOPに代表されるWebのセキュリティモデルが損なうため深刻度が⾼い
    32
    https://www.mozilla.org/en-US/security/client-bug-bounty/
    Mozillaでは深刻度最⾼の脆弱性
    (報奨⾦ $18,000)

    View full-size slide

  33. iOS版ブラウザアプリによくあるUXSS
    evaluateJavascript関数(後述)に起因するUXSSが発⽣しやすい
    過去にChromiumでも発⾒し、$10,000の報奨⾦を得たことがある
    33
    https://bugs.chromium.org/p/chromium/issues/detail?id=1164846
    evaluateJavascriptに起因するUXSS

    View full-size slide

  34. evaluateJavascript関数とは︖ 1/2
    iOSのブラウザアプリは全てWebページの表⽰にWKWebViewを使⽤している
    WKWebViewにはNative(Swift)⇔ Web(JS)間でやり取りする仕組みがある
    34
    iOSのブラウザアプリ
    Native(Swift) WKWebView(JS)

    View full-size slide

  35. evaluateJavascript関数とは︖ 2/2
    WebからNative側への通信には webkit.messageHandlers を使⽤する
    Native側からWebへの通信に使うのが evaluateJavascript 関数
    35
    iOSのブラウザアプリ
    Native(Swift) WKWebView(JS)
    Native(Swift)からWeb(JS)への通信に利⽤

    View full-size slide

  36. iOS版ブラウザアプリによくあるUXSSの仕組み
    iframe内のページから不正なデータをNative側に送ることにより、
    evaluateJavascriptを通じて親フレーム上で任意のJSを実⾏させることができる
    36
    iOSのブラウザアプリ
    Native(Swift) WKWebView(JS)
    '+alert(1)+'
    親フレーム(異なるサイト)上で実⾏される
    iframe内のページから不正なデータを送ると
    動的にJSが⽣成されて

    View full-size slide

  37. ⾒つけた脆弱性②︓FIDO U2F機能によるUXSS 1/3
    iframe内のページからFIDO U2Fの機能を介して不正なデータを送ることにより、
    親フレーム上で任意のJSを実⾏させることができるというもの(UXSS)
    37
    https://hackerone.com/reports/993670

    View full-size slide

  38. ⾒つけた脆弱性②︓FIDO U2F機能によるUXSS 2/3
    実⾏するJSのコードを⽂字列結合で組み⽴てており、さらにこの中で
    versionという変数はWebサイト側から取得した値であった
    38
    https://github.com/brave/brave-ios/blob/d01b8c07b8a6244af48798efe4afeccd266707e2/Client/WebAuthN/U2FExtensions.swift#L1003
    実⾏するJSのコードを⽂字列結合で組み⽴てている
    (変数versionはWebサイト側から取得した値)

    View full-size slide

  39. ⾒つけた脆弱性②︓FIDO U2F機能によるUXSS 3/3
    FIDOの認証デバイスを登録する関数の引数に不正なversionを指定することにより
    evaluateJavascriptを通じて親フレームのページ上で任意のJSが実⾏可能
    39
    https://hackerone.com/reports/993670
    Braveブラウザ
    Native(Swift) WKWebView(JS)
    '+alert(1)+'
    '+alert(1)+'
    親フレーム上でJSが実⾏される

    View full-size slide

  40. ⾒つけた脆弱性③︓Playlist機能によるUXSS 1/2
    同様にWebサイトの動画をプレイリストに追加する機能にも同様の実装があり
    変数 nodeTag を通じてUXSSが可能であった
    42
    https://hackerone.com/reports/1436558
    変数nodeTagはWebサイト側から取得した値

    View full-size slide

  41. ⾒つけた脆弱性③︓Playlist機能によるUXSS 2/2
    Googleサイト(sites.google.com)でサイトを構築し、攻撃⽤のページを
    フレームの中に埋め込むと…
    43
    https://sites.google.com/view/nishimunea-brave-uxss1/page
    攻撃⽤のページをフレームに埋め込む

    View full-size slide

  42. 謝辞
    今回の発表に際して、報告した脆弱性のうち13件の公開許可を頂きました
    ご対応頂いたBrave開発者の皆様に御礼を申し上げます
    46
    https://hackerone.com/nishimunea?type=user

    View full-size slide