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.1k
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Brave Browserの脆弱性を見つけた話(iOS編)
Shibuya.XSS techtalk #12の発表資料です。
MUNEAKI NISHIMURA
July 25, 2023
More Decks by MUNEAKI NISHIMURA
See All by MUNEAKI NISHIMURA
脆弱星に導かれて
nishimunea
3
3k
ブラウザの脆弱性とそのインパクト
nishimunea
26
10k
脆弱性発見者が注目する近年のWeb技術
nishimunea
29
13k
脆弱性発見者の目から見た、脆弱性対応の最前線
nishimunea
15
2.8k
Slack Team for Security Testers and Bug Hunters
nishimunea
1
850
Finding Vulnerabilities in Firefox for iOS
nishimunea
3
9.1k
SWIFT Code for Mozilla Bank
nishimunea
1
1k
次世代プラットフォームのセキュリティモデル考察
nishimunea
6
5.7k
Other Decks in Technology
See All in Technology
「早く出す」より「事業に効く」 ── 顧客の業務サイクルから逆算するAI時代の二重ループ開発と「変化の設計者」 / devsumi2026
rakus_dev
1
360
美しいコードを書くためにF#を学んでみた話
yud0uhu
1
460
DMM.com 購入改善推進チーム におけるCodeRabbitを用いた レビューフロー改善の一例
ysknsid25
2
660
OPENLOGI Company Profile for engineer
hr01
1
74k
CDKで書くECSのベストプラクティス、 改めて考え直す2026 #cdkconf2026
makies
3
770
Empower GenAI with Agile - あなたのアジャイルが生成AIのバフになる仕組み
hageyahhoo
1
210
End-to-Endで考える信頼性 —LINEアプリにおけるクライアント開発×SRE連携の実践
maruloop
4
4.5k
あなたの『Site』はどこですか? — xREという考え方
miyamu
0
1.2k
AI時代の開発生産性は、個人技からチーム設計へ
moongift
PRO
4
2.3k
AICoEでAIネイティブ組織への進化
yukiogawa
0
190
ガバナンスの「ちょうどいい落とし所」を探れ!開発スピードを妨げない運用判断の勘所 / SRE NEXT 2026
genda
1
240
カードゲーム作りが教えてくれた プロダクトオーナーシップ
moritamasami
0
110
Featured
See All Featured
Discover your Explorer Soul
emna__ayadi
2
1.2k
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
350
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.9k
Agile that works and the tools we love
rasmusluckow
331
22k
Neural Spatial Audio Processing for Sound Field Analysis and Control
skoyamalab
0
370
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.1k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
530
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
Abbi's Birthday
coloredviolet
3
8.7k
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!