Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
990
次世代プラットフォームのセキュリティモデル考察
nishimunea
6
5.5k
Other Decks in Technology
See All in Technology
CQRS/ESになぜアクターモデルが必要なのか
j5ik2o
0
450
BidiAgent と Nova 2 Sonic から考える音声 AI について
yama3133
2
140
AI with TiDD
shiraji
1
340
産業的変化も組織的変化も乗り越えられるチームへの成長 〜チームの変化から見出す明るい未来〜
kakehashi
PRO
1
270
コールドスタンバイ構成でCDは可能か
hiramax
0
130
スクラムマスターが スクラムチームに入って取り組む5つのこと - スクラムガイドには書いてないけど入った当初から取り組んでおきたい大切なこと -
scrummasudar
1
1.2k
Authlete で実装する MCP OAuth 認可サーバー #CIMD の実装を添えて
watahani
0
390
ハッカソンから社内プロダクトへ AIエージェント ko☆shi 開発で学んだ4つの重要要素
leveragestech
0
550
AIエージェントを5分で一気におさらい!AIエージェント「構築」元年に備えよう
yakumo
1
140
Master Dataグループ紹介資料
sansan33
PRO
1
4.2k
#22 CA × atmaCup 3rd 1st Place Solution
yumizu
1
120
RALGO : AIを組織に組み込む方法 -アルゴリズム中心組織設計- #RSGT2026 / RALGO: How to Integrate AI into an Organization – Algorithm-Centric Organizational Design
kyonmm
PRO
3
620
Featured
See All Featured
How to Talk to Developers About Accessibility
jct
1
94
AI: The stuff that nobody shows you
jnunemaker
PRO
1
150
Faster Mobile Websites
deanohume
310
31k
Un-Boring Meetings
codingconduct
0
170
A Modern Web Designer's Workflow
chriscoyier
698
190k
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
41
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
78
Building Applications with DynamoDB
mza
96
6.9k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
74
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!