$30 off During Our Annual Pro Sale. View Details »
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
470
WCTF2019: Gyotaku The Flag
icchy
0
290
Other Decks in Technology
See All in Technology
[GDG DevFest Bangkok 2024] - The Future of Retail E-commerce with Gemini AI
punsiriboo
0
300
ARRが3年で10倍になったプロダクト開発とAI活用の軌跡
akiroom
0
240
Explain EXPLAIN
keiko713
10
2.7k
宇宙最速のランチRecap LT会(開発者ツール&運用監視編)
nnydtmg
1
170
【AWS re:Invent 2024】Amazon Bedrock アップデート総まとめ
minorun365
PRO
7
520
間違いだらけのポストモーテム - ホントに役立つレビューはこうだ!
jacopen
5
990
まだチケットを手動で書いてるの?!GitHub Actionsと生成AIでチケットの作成を自動化してみた話 / 20241207 Yoshinori Katayama
shift_evolve
1
760
Oracle Base Database Service:サービス概要のご紹介
oracle4engineer
PRO
0
15k
GeminiとUnityで実現するインタラクティブアート
hokkey621
0
620
2000年てづくりキーボードの旅
tagomoris
1
160
Kubernetesを知る
logica0419
18
5k
[DevFestTokyo]Accelerating Flutter App Development Using Generative AI
korodroid
1
340
Featured
See All Featured
The Art of Programming - Codeland 2020
erikaheidi
53
13k
Code Review Best Practice
trishagee
64
17k
Imperfection Machines: The Place of Print at Facebook
scottboms
266
13k
Embracing the Ebb and Flow
colly
84
4.5k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
6.9k
How to Ace a Technical Interview
jacobian
276
23k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
45
2.2k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
26
1.4k
RailsConf 2023
tenderlove
29
920
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
A Philosophy of Restraint
colly
203
16k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
6
480
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/