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
React Hooksに潜む罠
Search
icchy
February 22, 2023
Technology
0
2.9k
React Hooksに潜む罠
2023/02/22
Security.Tokyo #1
icchy
February 22, 2023
Tweet
Share
More Decks by icchy
See All by icchy
Let's Make Windows Defender Angry: Antivirus can be an oracle!
icchy
3
8.2k
アンチウイルスをオラクルとした Windows Defenderに対する 新しい攻撃手法
icchy
0
580
WCTF2019: Gyotaku The Flag
icchy
0
360
Other Decks in Technology
See All in Technology
不確実性に備える ABEMA の信頼性設計とオブザーバビリティ基盤
nagapad
4
9k
Pandocでmd→pptx便利すぎワロタwww
meow_noisy
2
990
AS59105におけるFreeBSD EtherIPの運用と課題
x86taka
0
290
AI開発の定着を推進するために揃えるべき前提
suguruooki
1
420
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
2.9k
『星の世界の地図の話: Google Sky MapをAI Agentでよみがえらせる』 - Google Developers DevFest Tokyo 2025
taniiicom
0
420
Excelデータ分析で学ぶディメンショナルモデリング ~アジャイルデータモデリングへ向けて~ by @Kazaneya_PR / 20251126
kazaneya
PRO
3
610
Active Directory 勉強会 第 6 回目 Active Directory セキュリティについて学ぶ回
eurekaberry
3
1.4k
その意思決定、まだ続けるんですか? ~痛みを超えて未来を作る、AI時代の撤退とピボットの技術~
applism118
42
24k
生成AIシステムとAIエージェントに関する性能や安全性の評価
shibuiwilliam
2
250
グローバルなコンパウンド戦略を支えるモジュラーモノリスとドメイン駆動設計
kawauso
3
10k
社内外から"使ってもらえる"データ基盤を支えるアーキテクチャの秘訣/登壇資料(飯塚 大地・高橋 一貴)
hacobu
PRO
0
8.2k
Featured
See All Featured
Facilitating Awesome Meetings
lara
57
6.6k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Writing Fast Ruby
sferik
630
62k
[RailsConf 2023] Rails as a piece of cake
palkan
57
6.1k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
1.6k
The Art of Programming - Codeland 2020
erikaheidi
56
14k
Music & Morning Musume
bryan
46
7k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.1k
Scaling GitHub
holman
464
140k
A Modern Web Designer's Workflow
chriscoyier
697
190k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
37
2.6k
How to Think Like a Performance Engineer
csswizardry
28
2.3k
Transcript
React Hooksに潜む罠 Security.Tokyo #1 icchy
whoami • セキュリティエンジニアをやっている • 社内で使うツールを書く必要があり、なんとなくReactを始めた ◦ 2年前くらい ◦ 普段から書いているわけではない ◦
つまり初心者 • **フロントエンドの専門家ではありません** ◦ 発表内容には間違いがある可能性があります ◦ ソースコードもそこまで深く読んではいません • CTFをやっていた(過去)
今日お話しする内容について • React Hooksの既知の挙動 ◦ 0-dayではありません • CTFの問題として取り上げられた ◦ picoCTF
2022 "live-art" by Zachary Wade (@zwad3) ◦ 脆弱性のある実装パターンを問題にしたのはおそらくこれが初出 ◦ https://github.com/zwade/live-art/blob/master/solution/writeup.md • 詳しく調べてみたところ、かなり面白いバグだったので紹介 ◦ (おそらく)一般的なプロダクトでもやってしまいそうな気がする • 発表者がReactにあまり明るくありません ◦ 変な間違いがあったら後でこっそり教えてください ◦ 憶測で書いている箇所は斜体かつオレンジにしてあります
Reactとは?
React • "ユーザインターフェース構築のための JavaScript ライブラリ" • Jordan Walke (元Facebook) が開発、2013年にOSS化
◦ 以降はFacebook (Meta) やコミュニティによって継続的に開発 • JSX (TSX) で書かれた宣言的UI ◦ 変なDOM操作をしなくてよい • XSSが発生しにくい • 状態管理をするための機能が用意されている ◦ React 16.8 からReact Hooksと呼ばれるシンプルなものが導入された ◦ クラスではなく関数として使用可能
命令的UIと宣言的UI • 命令的 (Imperative) ◦ 目的に向けた過程を記述 ◦ DOMを指定して、その中に値をセット ◦ 仕様を知らないと完成形がイメージできない
• 宣言的 (Declarative) ◦ 最終的にどうしたいかを記述 ◦ テンプレートを用意する ◦ 完成形が比較的イメージしやすい • UIを作る目的においては宣言的の方が都合が良い
None
Reactって何のためにあるの? • 一般にブラウザがDOMを描画するコストは高い ◦ 不必要な計算はなるべくしないことが大事 ◦ UIに限らずあらゆるアルゴリズムで言えること • 差分検出処理 (Reconcilation)
をよしなにやる ◦ 状態遷移の前後で異なる DOMだけを計算する ▪ 必要最低限のDOMを再描画する ◦ Reactに限らずVue.jsでも同じようなことをやっている (patch) • なので(レンダリングが)早い、(メモリも)軽い https://ja.reactjs.org/docs/rendering-elements.html
Reactって何やってるの? • 差分検出をめちゃくちゃ賢くやっている ◦ https://ja.reactjs.org/docs/reconciliation.html • React 16からReact Fiberと呼ばれるエンジンに変わった ◦
https://github.com/acdlite/react-fiber-architecture • 状態管理やメモ化のための機能を提供している → React Hooks
React Hooks
React Hooks • コンポーネントが内部で持つ状態を管理することができる • プログラマは「状態遷移の処理」を書く ◦ 処理の結果、何を描画すべきということについては Reactが管理 clicked:
1 button clicked: 2 button count = count + 1 count: 1 count: 2
React Hooks import React, { useState } from 'react'; function
Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
React Hooks import React, { useState } from 'react'; function
Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
React Hooks import React, { useState } from 'react'; function
Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } 次の状態を決定する処理を書く 初期値
React Hooks import React, { useState } from 'react'; function
Example() { // Declare a new state variable, which we'll call "count" const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } この部分だけ再描画される
React Hooksはどうやって実現しているの? • packages/react-reconciler/src/ReactFiberHooks.js ◦ FiberはいわゆるComponentの内部状態を管理するためのもの ◦ HooksはFiberのmemoizedStateに単方向リストとして保存 • あるコンポーネントのHooksを処理するときは
◦ memoizedStateのHooksを辿っていき、順に処理する → Hooksは必ずある一つのFiberに属する
React Hooksを書く時に必ず守らなければならないこと • https://ja.reactjs.org/docs/hooks-rules.html ◦ フックを呼び出すのはトップレベルのみ ◦ フックを呼び出すのは React の関数内のみ
• つまり ◦ 条件分岐の中で呼び出したり ◦ ネストされた関数の中で呼び出したり → 使うHooksが一意ではない呼び出し方をしてはいけません
脆弱性を指摘できますか?
<Component … /> vs Component(props)
<Component {...props} /> と Component(props) の違いは? • <Component … />
== React.createElement(Component, …) ◦ https://ja.reactjs.org/docs/jsx-in-depth.html ◦ "JSX とは、つまるところ React.createElement(component, props, ...children) の 糖衣構文にすぎません。 " ◦ Component(props)とは明確に異なる • Hooksを管理する上でこの違いは非常に重要 ◦ いま呼び出そうとしている関数に Hooksが含まれている可能性があるか?が非常に重要 ◦ つまりComponent(props)は「フックを呼び出すのは React の関数内のみ」に違反する
通常の挙動 (<Component />)
通常の挙動 (<Component />)
通常の挙動 (<Component />) App:useState(0) count: 0 Hooks List
通常の挙動 (<Component />) App:useState(0) count: 0 Hooks List EvenComponent:useState("I'm Even")
msg: I'm Even
通常の挙動 (<Component />) App:useState(0) count: 0 Hooks List EvenComponent:useState("I'm Even")
msg: I'm Even
通常の挙動 (<Component />) App:useState(0) count: 0 Hooks List EvenComponent:useState("I'm Even")
msg: I'm Even
通常の挙動 (<Component />) App:useState(0) count: 0 Hooks List App:useState(0) count:
1 初期化せずに再利用 EvenComponent:useState("I'm Even") msg: I'm Even
通常の挙動 (<Component />) App:useState(0) count: 1 Hooks List EvenComponent:useState("I'm Even")
msg: I'm Even
通常の挙動 (<Component />) App:useState(0) count: 1 Hooks List EvenComponent:useState("I'm Even")
msg: I'm Even OddComponent:useState("I'm Odd") msg: I'm Odd 異なるコンポーネントなので初期化
通常の挙動 (<Component />) App:useState(0) count: 1 Hooks List OddComponent:useState("I'm Odd")
msg: I'm Odd
通常の挙動 (<Component />) App:useState(0) count: 1 Hooks List OddComponent:useState("I'm Odd")
msg: I'm Odd
None
間違った実装 (Component({...}))
間違った実装 (Component({...})) App:useState(0) count: 0 Hooks List
間違った実装 (Component({...})) App:useState(0) count: 0 Hooks List App:useState("I'm Even") msg:
I'm Even
間違った実装 (Component({...})) App:useState(0) count: 0 Hooks List App:useState("I'm Even") msg:
I'm Even
間違った実装 (Component({...})) App:useState(0) count: 0 Hooks List App:useState("I'm Even") msg:
I'm Even
間違った実装 (Component({...})) App:useState(0) count: 1 Hooks List App:useState("I'm Even") msg:
I'm Even
間違った実装 (Component({...})) App:useState(0) count: 1 Hooks List App:useState("I'm Even") msg:
I'm Even App:useState("I'm Odd") msg: I'm Odd 初期化せずに再利用
間違った実装 (Component({...})) App:useState(0) count: 1 Hooks List App:useState("I'm Odd") msg:
I'm Even
間違った実装 (Component({...})) App:useState(0) count: 1 Hooks List App:useState("I'm Odd") msg:
I'm Even
None
これってヤバいんですか? • ヤバいです • 次のようなコードを考えるとわかりやすい
これってヤバいんですか? • ヤバいです • 次のようなコードを考えるとわかりやすい スプレッド構文 <img src=... height="100" width="100"
/> オブジェクトの中身が展開される
つまり • コンポーネントの使い方によってはimgタグの属性をコントロール可能 • scale = { src: "x", onerror:
"alert(1);" } → <img src="x" onerror="alert(1);"> →
このバグを整理すると • 以下の条件が揃うと脆弱性となる ◦ 攻撃者がある程度データの中身をコントロール可能な Stateがある(source) ◦ Stateの内容によっては予期せぬ挙動を引き起こす箇所がある( sink) ◦
以上の2箇所のStateの取り違いが起こる箇所がある( confusion) ◦ 取り違いが起こっても呼ばれる Hooksの数は変わらない(layout) source, sink, confusion, layoutという名前は一般的な用語ではありません • 例 ◦ クエリ文字列をオブジェクトに変換している( source) ◦ スプレッド構文でwidth, heightをimgタグに展開している( sink)
このバグは検知可能ですか?
一応可能です • Reactはすごいのであの手この手でバグる可能性があることを教えてくれる ◦ Devモード ▪ 呼ばれているHooksの種類が変わったら consoleを警告で真っ赤に染めてくれる ◦ eslint
▪ plugins:react-hooks/recommendedを足すと教えてくれる ▪ • 静的にも動的にも検出する努力をしている! ◦ 本当に十分?
実は十分ではない • Reactはすごいのであの手この手でバグる可能性があることを教えてくれる? ◦ Devモード ▪ 呼ばれているHooksの種類が変わったら consoleを警告で真っ赤に染めてくれる → Hooksの種類が変わらないと教えてくれない
◦ eslint ▪ plugins:react-hooks/recommendedを足すと教えてくれる → CRA (create-react-app) や Vite (create-vite) でOptional → 標準の設定では検知できない
実は十分ではない • すべてのチェックをすり抜けるパターン: 例で挙げたやつ
なぜ十分でないのか? • ESLintのルールが雑 ◦ フックが必ずReactの関数内で呼ばれているか?はチェックしてくれる ▪ Reactの関数内か? → 関数名しかチェックしない( Camelcase
or useHookName) ◦ React.createElementとして呼び出しているか?はチェックしてくれない • 検知しにくい → いろんなプロダクトに潜んでいる可能性 ◦ Reactで書かれたUIは複雑になりがちなので多分 OSSにもある ◦ バグバウンティチャンス ◦ これを検知するESLintとかを書いたら喜ばれるかも
問題となりやすい実装
Case 1: callbackの中で呼んでいる • どちらのuseStateが先に呼ばれるか決定できない • 二回目のrender時に逆転する可能性 • eslint: 検知
• dev: 検知 • prod: 検知
Case 2: 条件分岐の中で呼んでいる • useState<Data[]> • useState<AnonymousData[]> でconfusion • eslint:
検知 • dev: 部分的に検知 ◦ Hooksの種類が 異なる場合のみ • prod: 検知不可
Case 3: 関数の中から呼んでいる • 例で使ったパターン • eslint: 検知不可 • dev:
部分的に検知 ◦ Hooksの種類が 異なる場合のみ • prod: 検知不可
対策とまとめ
対策方法 • 基本的に発火する条件が厳しい ◦ source, sink, confusion, layoutがすべて揃うことが必要 • 開発者が気をつけなければならない箇所:
confusion ◦ 他の条件は防ぎようがない • 関数呼び出しは検出できないので気を付ける
まとめ • React Hooksのルールを守りましょう ◦ フックを呼び出すのはトップレベルのみ ◦ フックを呼び出すのは Reactの関数内のみ •
(多分)いろんなところに潜んでいる ◦ 是非探してみてください
参考文献 • https://github.com/zwade/live-art/blob/master/solution/writeup.md • https://github.com/facebook/react • https://ja.reactjs.org/docs/hooks-rules.html • https://github.com/acdlite/react-fiber-architecture •
https://ja.reactjs.org/docs/reconciliation.html • https://sbfl.net/blog/2019/02/09/react-hooks-usestate/