Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Brave Browserの脆弱性を見つけた話(iOS編)
Search
MUNEAKI NISHIMURA
July 25, 2023
Technology
3
2.9k
Brave Browserの脆弱性を見つけた話(iOS編)
Shibuya.XSS techtalk #12の発表資料です。
MUNEAKI NISHIMURA
July 25, 2023
Tweet
Share
More Decks by MUNEAKI NISHIMURA
See All by MUNEAKI NISHIMURA
脆弱星に導かれて
nishimunea
3
2.8k
ブラウザの脆弱性とそのインパクト
nishimunea
26
10k
脆弱性発見者が注目する近年のWeb技術
nishimunea
29
13k
脆弱性発見者の目から見た、脆弱性対応の最前線
nishimunea
15
2.8k
Slack Team for Security Testers and Bug Hunters
nishimunea
1
820
Finding Vulnerabilities in Firefox for iOS
nishimunea
3
8.9k
SWIFT Code for Mozilla Bank
nishimunea
1
980
次世代プラットフォームのセキュリティモデル考察
nishimunea
6
5.5k
Other Decks in Technology
See All in Technology
日本Rubyの会の構造と実行とあと何か / hokurikurk01
takahashim
4
620
Symfony AI in Action
el_stoffel
2
370
“決まらない”NSM設計への処方箋 〜ビットキーにおける現実的な指標デザイン事例〜 / A Prescription for "Stuck" NSM Design: Bitkey’s Practical Case Study
bitkey
PRO
1
370
こがヘンだよ!Snowflake?サービス名称へのこだわり
tarotaro0129
0
110
タグ付きユニオン型を便利に使うテクニックとその注意点
uhyo
2
670
ページの可視領域を算出する方法について整理する
yamatai1212
0
160
How native lazy objects will change Doctrine and Symfony forever
beberlei
1
390
Agentic AI Patterns and Anti-Patterns
glaforge
1
100
Introduction to Bill One Development Engineer
sansan33
PRO
0
330
Sansan Engineering Unit 紹介資料
sansan33
PRO
1
3.3k
技術以外の世界に『越境』しエンジニアとして進化を遂げる 〜Kotlinへの愛とDevHRとしての挑戦を添えて〜
subroh0508
1
160
freeeにおけるファンクションを超えた一気通貫でのAI活用
jaxx2104
3
940
Featured
See All Featured
Leading Effective Engineering Teams in the AI Era
addyosmani
8
1.3k
A better future with KSS
kneath
240
18k
Rebuilding a faster, lazier Slack
samanthasiow
84
9.3k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Reflections from 52 weeks, 52 projects
jeffersonlam
355
21k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.3k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.5k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
Why You Should Never Use an ORM
jnunemaker
PRO
60
9.6k
GitHub's CSS Performance
jonrohan
1032
470k
Designing for Performance
lara
610
69k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
140
34k
Transcript
Brave Browserの脆弱性を⾒つけた話(iOS編) Shibuya.XSS techtalk #12 Muneaki Nishimura (nishimunea)
⾃⼰紹介 ⻄村 宗晃(にしむねあ) ブラウザの脆弱性を探すのが⼤好き 好きなブラウザはFirefox https://www.slideshare.net/codeblue_jp/firefox-by https://files.speakerdeck.com/presentations/3cf321f5ee4d4aaba9f18305ee2a461c/SWIFT_Code_for_Mozilla_Bank__nishimunea_.pdf 1
Braveとは︖ JavaScriptの⽣みの親 Brendan Eich⽒が 開発したオープンソースのWebブラウザ 広告ブロック、暗号資産連携、Torを⽤いた プライベートブラウズモード等の野⼼的な 機能を多数搭載 2
Braveの脆弱性を探し始めた経緯 1/3 僕もブラウザの脆弱性を 探してみたいのですが… 最初は簡単そうなところ が良いね。HackerOneに Braveってブラウザが あったけどどうかな︖ インターン⽣ わし
きっかけは当時、弊社に来ていたインターン⽣との会話 3
Braveの脆弱性を探し始めた経緯 2/3 思い付きで他⼈に勧めるのも良くないので、試しに⾃分でも探してみたところ… ちょっと⾃分でも 探してみるか… わし 4
Braveの脆弱性を探し始めた経緯 3/3 気付いたら、誰よりもBraveの脆弱性探してるマンになっていた(発表時点) https://hackerone.com/brave/thanks わし 5
本⽇は⾒つけた脆弱性を3つ紹介 6 XSS関連
リーダーモードに存在したXSS
リーダーモードとは︖ Webページから余計なものを削除して、本⽂を読みやすくしてくれる機能 押す 8
Braveアプリ内の HTMLテンプレート リーダーモードの仕組み 1/2 元ページからタイトル、クレジット、本⽂を抽出し、Braveがアプリ内に保持する HTMLテンプレートに埋め込む タイトル クレジット 本⽂ 9
Braveアプリ内の HTMLテンプレートに 埋め込んだ結果 リーダーモードの仕組み 2/2 埋め込んだ結果をアプリ内のローカルHTTPサーバから配信して表⽰ 表⽰時にはCSP(Content Security Policy)を付与することによりXSSを防⽌ XSS防⽌のためCSPを付与
script-src: 'nonce-XXXXX' 10
⾒つけた脆弱性①︓CSPのnonce制限を迂回したXSS 元ページからクレジットを埋め込む箇所にHTMLインジェクションがあった しかしCSP(script-srcのnonce制限)が効いているためXSSは困難… 著者名に<s>タグを挿⼊ 11 https://hackerone.com/reports/1436142 <s>タグが効いてる
CSPのnonceはどのように埋め込まれるのか︖ HTMLテンプレート内に埋め込まれた特殊⽂字列をNative側(Swift)で置換する 12 https://github.com/brave/brave-ios/pull/4209/files#diff-eaeef15a290e9e5e9bcaae784f18d874f8c932dfa3de416a5820eccd6b2d8cfbR54 <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById("reader-title").textContent
= "%READER-TITLE%"; </script> <div> %READER-CONTENT% </div> 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コード
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コード <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById("reader-title").textContent = "%READER-TITLE%"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
HTMLテンプレート置換処理の実際の動き 2/8 はじめに元のページのタイトルが埋め込まれる 14 <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent
= "Title"; </script> <div> %READER-CONTENT% </div> 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コード
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) <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div>Credit</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div>Credit</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div>Credit</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div>Credit</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div>Credit</div> <script nonce="80841642004944135718143322189292"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
Braveアプリ内の HTMLテンプレート お気づきになりましたか… scriptタグのnonceに「%READER-TITLE-NONCE%」という⽂字列を指定すると、 Braveが正しいnonceに置き換えてくれる %READER-TITLE-NONCE% タイトル 本⽂ 21 https://hackerone.com/reports/1436142
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コード <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById("reader-title").textContent = "%READER-TITLE%"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
HTMLテンプレート置換処理の実際の動き(再掲) 2/8 はじめに元のページのタイトルが埋め込まれる 23 <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent
= "Title"; </script> <div> %READER-CONTENT% </div> 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コード
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) <h1 id="reader-title"></h1> <div>%READER-CREDITS%</div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div> </div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div> </div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> %READER-CONTENT% </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div> </div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div> %READER-TITLE-NONCE% </div> <script nonce="%READER-TITLE-NONCE%"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
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) <h1 id="reader-title"></h1> <div> 80841642004944135718143322189292 </div> <script nonce="80841642004944135718143322189292"> document.getElementById(“reader-title”).textContent = "Title"; </script> <div> Contents </div> HTMLテンプレート
30
様々なUniversal XSS
Universal XSS(UXSS)とは︖ 対象のサイトにXSSの脆弱性が無くてもXSSで攻撃できてしまうブラウザの脆弱性 SOPに代表されるWebのセキュリティモデルが損なうため深刻度が⾼い 32 https://www.mozilla.org/en-US/security/client-bug-bounty/ Mozillaでは深刻度最⾼の脆弱性 (報奨⾦ $18,000)
iOS版ブラウザアプリによくあるUXSS evaluateJavascript関数(後述)に起因するUXSSが発⽣しやすい 過去にChromiumでも発⾒し、$10,000の報奨⾦を得たことがある 33 https://bugs.chromium.org/p/chromium/issues/detail?id=1164846 evaluateJavascriptに起因するUXSS
evaluateJavascript関数とは︖ 1/2 iOSのブラウザアプリは全てWebページの表⽰にWKWebViewを使⽤している WKWebViewにはNative(Swift)⇔ Web(JS)間でやり取りする仕組みがある 34 iOSのブラウザアプリ Native(Swift) WKWebView(JS)
evaluateJavascript関数とは︖ 2/2 WebからNative側への通信には webkit.messageHandlers を使⽤する Native側からWebへの通信に使うのが evaluateJavascript 関数 35 iOSのブラウザアプリ
Native(Swift) WKWebView(JS) Native(Swift)からWeb(JS)への通信に利⽤
iOS版ブラウザアプリによくあるUXSSの仕組み iframe内のページから不正なデータをNative側に送ることにより、 evaluateJavascriptを通じて親フレーム上で任意のJSを実⾏させることができる 36 iOSのブラウザアプリ Native(Swift) WKWebView(JS) '+alert(1)+' 親フレーム(異なるサイト)上で実⾏される iframe内のページから不正なデータを送ると
動的にJSが⽣成されて
⾒つけた脆弱性②︓FIDO U2F機能によるUXSS 1/3 iframe内のページからFIDO U2Fの機能を介して不正なデータを送ることにより、 親フレーム上で任意のJSを実⾏させることができるというもの(UXSS) 37 https://hackerone.com/reports/993670
⾒つけた脆弱性②︓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サイト側から取得した値)
⾒つけた脆弱性②︓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が実⾏される
40
41
⾒つけた脆弱性③︓Playlist機能によるUXSS 1/2 同様にWebサイトの動画をプレイリストに追加する機能にも同様の実装があり 変数 nodeTag を通じてUXSSが可能であった 42 https://hackerone.com/reports/1436558 変数nodeTagはWebサイト側から取得した値
⾒つけた脆弱性③︓Playlist機能によるUXSS 2/2 Googleサイト(sites.google.com)でサイトを構築し、攻撃⽤のページを フレームの中に埋め込むと… 43 https://sites.google.com/view/nishimunea-brave-uxss1/page 攻撃⽤のページをフレームに埋め込む
44
最後に
謝辞 今回の発表に際して、報告した脆弱性のうち13件の公開許可を頂きました ご対応頂いたBrave開発者の皆様に御礼を申し上げます 46 https://hackerone.com/nishimunea?type=user
Thanks!