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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
830
Finding Vulnerabilities in Firefox for iOS
nishimunea
3
8.9k
SWIFT Code for Mozilla Bank
nishimunea
1
990
次世代プラットフォームのセキュリティモデル考察
nishimunea
6
5.6k
Other Decks in Technology
See All in Technology
All About Sansan – for New Global Engineers
sansan33
PRO
1
1.3k
Kiro IDEのドキュメントを全部読んだので地味だけどちょっと嬉しい機能を紹介する
khmoryz
0
110
分析画面のクリック操作をそのままコード化 ! エンジニアとビジネスユーザーが共存するAI-ReadyなBI基盤
ikumi
0
120
(金融庁共催)第4回金融データ活用チャレンジ勉強会資料
takumimukaiyama
0
100
茨城の思い出を振り返る ~CDKのセキュリティを添えて~ / 20260201 Mitsutoshi Matsuo
shift_evolve
PRO
1
110
CDK対応したAWS DevOps Agentを試そう_20260201
masakiokuda
1
130
We Built for Predictability; The Workloads Didn’t Care
stahnma
0
120
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
2.9k
Application Performance Optimisation in Practice (60 mins)
stevejgordon
0
110
みんなだいすきALB、NLBの 仕組みから最新機能まで総おさらい / Mastering ALB & NLB: Internal Mechanics and Latest Innovations
kaminashi
0
200
Amazon Bedrock AgentCore 認証・認可入門
hironobuiga
2
480
入社1ヶ月でデータパイプライン講座を作った話
waiwai2111
1
210
Featured
See All Featured
A Soul's Torment
seathinner
5
2.2k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
196
71k
sira's awesome portfolio website redesign presentation
elsirapls
0
140
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
62
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
The SEO Collaboration Effect
kristinabergwall1
0
350
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
9.5k
Building a A Zero-Code AI SEO Workflow
portentint
PRO
0
290
The Curse of the Amulet
leimatthew05
1
8k
Optimizing for Happiness
mojombo
379
71k
Utilizing Notion as your number one productivity tool
mfonobong
2
210
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!