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
Opcodeを読んでいたら何故かphp-srcを読んでいた話
murashotaro
0
170
多領域インシデントマネジメントへの挑戦:ハードウェアとソフトウェアの融合が生む課題/Challenge to multidisciplinary incident management: Issues created by the fusion of hardware and software
bitkey
PRO
2
100
Jetpack Composeで始めるServer Cache State
ogaclejapan
2
170
watsonx.ai Dojo #5 ファインチューニングとInstructLAB
oniak3ibm
PRO
0
160
GitHub Copilot のテクニック集/GitHub Copilot Techniques
rayuron
27
12k
Wvlet: A New Flow-Style Query Language For Functional Data Modeling and Interactive Data Analysis - Trino Summit 2024
xerial
1
110
宇宙ベンチャーにおける最近の情シス取り組みについて
axelmizu
0
110
PHPからGoへのマイグレーション for DMMアフィリエイト
yabakokobayashi
1
170
ブラックフライデーで購入したPixel9で、Gemini Nanoを動かしてみた
marchin1989
1
520
AIのコンプラは何故しんどい?
shujisado
1
190
alecthomas/kong はいいぞ / kamakura.go#7
fujiwara3
1
300
生成AIのガバナンスの全体像と現実解
fnifni
1
180
Featured
See All Featured
Writing Fast Ruby
sferik
628
61k
Statistics for Hackers
jakevdp
796
220k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
247
1.3M
The Invisible Side of Design
smashingmag
298
50k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
226
22k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.7k
Building Your Own Lightsaber
phodgson
103
6.1k
How to Ace a Technical Interview
jacobian
276
23k
VelocityConf: Rendering Performance Case Studies
addyosmani
326
24k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.3k
Building Adaptive Systems
keathley
38
2.3k
The Power of CSS Pseudo Elements
geoffreycrofte
73
5.4k
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/