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.7k
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.1k
アンチウイルスをオラクルとした Windows Defenderに対する 新しい攻撃手法
icchy
0
560
WCTF2019: Gyotaku The Flag
icchy
0
350
Other Decks in Technology
See All in Technology
オブザーバビリティプラットフォーム開発におけるオブザーバビリティとの向き合い / Hatena Engineer Seminar #34 オブザーバビリティの実現と運用編
arthur1
0
350
データ基盤の管理者からGoogle Cloud全体の管理者になっていた話
zozotech
PRO
0
340
【CEDEC2025】『ウマ娘 プリティーダービー』における映像制作のさらなる高品質化へ!~ 豊富な素材出力と制作フローの改善を実現するツールについて~
cygames
PRO
0
230
【CEDEC2025】大規模言語モデルを活用したゲーム内会話パートのスクリプト作成支援への取り組み
cygames
PRO
2
770
Segment Anything Modelの最新動向:SAM2とその発展系
tenten0727
0
450
Claude CodeでKiroの仕様駆動開発を実現させるには...
gotalab555
3
900
2025-07-31: GitHub Copilot Agent mode at Vibe Coding Cafe (15min)
chomado
2
380
「AIと一緒にやる」が当たり前になるまでの奮闘記
kakehashi
PRO
3
100
帳票構造化タスクにおけるLLMファインチューニングの性能評価
yosukeyoshida
1
230
人に寄り添うAIエージェントとアーキテクチャ #BetAIDay
layerx
PRO
8
2k
Kiroから考える AIコーディングツールの潮流
s4yuba
4
670
データモデリング通り #2オンライン勉強会 ~方法論の話をしよう~
datayokocho
0
110
Featured
See All Featured
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Writing Fast Ruby
sferik
628
62k
Large-scale JavaScript Application Architecture
addyosmani
512
110k
Typedesign – Prime Four
hannesfritz
42
2.7k
VelocityConf: Rendering Performance Case Studies
addyosmani
332
24k
Documentation Writing (for coders)
carmenintech
73
5k
GitHub's CSS Performance
jonrohan
1031
460k
KATA
mclloyd
31
14k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
750
Mobile First: as difficult as doing things right
swwweet
223
9.9k
Build your cross-platform service in a week with App Engine
jlugia
231
18k
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/