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.2k
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
7.9k
アンチウイルスをオラクルとした Windows Defenderに対する 新しい攻撃手法
icchy
0
500
WCTF2019: Gyotaku The Flag
icchy
0
290
Other Decks in Technology
See All in Technology
OpenAIの蒸留機能(Model Distillation)を使用して運用中のLLMのコストを削減する取り組み
pharma_x_tech
4
560
社外コミュニティで学び社内に活かす共に学ぶプロジェクトの実践/backlogworld2024
nishiuma
0
270
ハイテク休憩
sat
PRO
2
160
podman_update_2024-12
orimanabu
1
280
re:Invent 2024 Innovation Talks(NET201)で語られた大切なこと
shotashiratori
0
320
普通のエンジニアがLaravelコアチームメンバーになるまで
avosalmon
0
110
継続的にアウトカムを生み出し ビジネスにつなげる、 戦略と運営に対するタイミーのQUEST(探求)
zigorou
0
600
UI State設計とテスト方針
rmakiyama
2
630
マイクロサービスにおける容易なトランザクション管理に向けて
scalar
0
140
NilAway による静的解析で「10 億ドル」を節約する #kyotogo / Kyoto Go 56th
ytaka23
3
380
Oracle Cloud Infrastructure:2024年12月度サービス・アップデート
oracle4engineer
PRO
0
210
20241214_WACATE2024冬_テスト設計技法をチョット俯瞰してみよう
kzsuzuki
3
540
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
GraphQLとの向き合い方2022年版
quramy
44
13k
YesSQL, Process and Tooling at Scale
rocio
169
14k
Embracing the Ebb and Flow
colly
84
4.5k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
111
49k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
It's Worth the Effort
3n
183
28k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
17
2.3k
How GitHub (no longer) Works
holman
311
140k
BBQ
matthewcrist
85
9.4k
How to train your dragon (web standard)
notwaldorf
88
5.7k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
132
33k
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/