Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Hooksに潜む罠

icchy
February 22, 2023

React Hooksに潜む罠

2023/02/22
Security.Tokyo #1

icchy

February 22, 2023
Tweet

More Decks by icchy

Other Decks in Technology

Transcript

  1. React Hooksに潜む罠
    Security.Tokyo #1
    icchy

    View full-size slide

  2. whoami
    ● セキュリティエンジニアをやっている
    ● 社内で使うツールを書く必要があり、なんとなくReactを始めた
    ○ 2年前くらい
    ○ 普段から書いているわけではない
    ○ つまり初心者
    ● **フロントエンドの専門家ではありません**
    ○ 発表内容には間違いがある可能性があります
    ○ ソースコードもそこまで深く読んではいません
    ● CTFをやっていた(過去)

    View full-size slide

  3. 今日お話しする内容について
    ● 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にあまり明るくありません
    ○ 変な間違いがあったら後でこっそり教えてください
    ○ 憶測で書いている箇所は斜体かつオレンジにしてあります

    View full-size slide

  4. Reactとは?

    View full-size slide

  5. React
    ● "ユーザインターフェース構築のための JavaScript ライブラリ"
    ● Jordan Walke (元Facebook) が開発、2013年にOSS化
    ○ 以降はFacebook (Meta) やコミュニティによって継続的に開発
    ● JSX (TSX) で書かれた宣言的UI
    ○ 変なDOM操作をしなくてよい
    ● XSSが発生しにくい
    ● 状態管理をするための機能が用意されている
    ○ React 16.8 からReact Hooksと呼ばれるシンプルなものが導入された
    ○ クラスではなく関数として使用可能

    View full-size slide

  6. 命令的UIと宣言的UI
    ● 命令的 (Imperative)
    ○ 目的に向けた過程を記述
    ○ DOMを指定して、その中に値をセット
    ○ 仕様を知らないと完成形がイメージできない
    ● 宣言的 (Declarative)
    ○ 最終的にどうしたいかを記述
    ○ テンプレートを用意する
    ○ 完成形が比較的イメージしやすい
    ● UIを作る目的においては宣言的の方が都合が良い

    View full-size slide

  7. Reactって何のためにあるの?
    ● 一般にブラウザがDOMを描画するコストは高い
    ○ 不必要な計算はなるべくしないことが大事
    ○ UIに限らずあらゆるアルゴリズムで言えること
    ● 差分検出処理 (Reconcilation) をよしなにやる
    ○ 状態遷移の前後で異なる DOMだけを計算する
    ■ 必要最低限のDOMを再描画する
    ○ Reactに限らずVue.jsでも同じようなことをやっている (patch)
    ● なので(レンダリングが)早い、(メモリも)軽い
    https://ja.reactjs.org/docs/rendering-elements.html

    View full-size slide

  8. Reactって何やってるの?
    ● 差分検出をめちゃくちゃ賢くやっている
    ○ https://ja.reactjs.org/docs/reconciliation.html
    ● React 16からReact Fiberと呼ばれるエンジンに変わった
    ○ https://github.com/acdlite/react-fiber-architecture
    ● 状態管理やメモ化のための機能を提供している → React Hooks

    View full-size slide

  9. React Hooks
    ● コンポーネントが内部で持つ状態を管理することができる
    ● プログラマは「状態遷移の処理」を書く
    ○ 処理の結果、何を描画すべきということについては Reactが管理
    clicked: 1
    button
    clicked: 2
    button
    count = count + 1
    count: 1 count: 2

    View full-size slide

  10. 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 (

    You clicked {count} times
    setCount(count + 1)}>
    Click me


    );
    }

    View full-size slide

  11. 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 (

    You clicked {count} times
    setCount(count + 1)}>
    Click me


    );
    }

    View full-size slide

  12. 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 (

    You clicked {count} times
    setCount(count + 1)}>
    Click me


    );
    }
    次の状態を決定する処理を書く
    初期値

    View full-size slide

  13. 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 (

    You clicked {count} times
    setCount(count + 1)}>
    Click me


    );
    }
    この部分だけ再描画される

    View full-size slide

  14. React Hooksはどうやって実現しているの?
    ● packages/react-reconciler/src/ReactFiberHooks.js
    ○ FiberはいわゆるComponentの内部状態を管理するためのもの
    ○ HooksはFiberのmemoizedStateに単方向リストとして保存
    ● あるコンポーネントのHooksを処理するときは
    ○ memoizedStateのHooksを辿っていき、順に処理する
    → Hooksは必ずある一つのFiberに属する

    View full-size slide

  15. React Hooksを書く時に必ず守らなければならないこと
    ● https://ja.reactjs.org/docs/hooks-rules.html
    ○ フックを呼び出すのはトップレベルのみ
    ○ フックを呼び出すのは React の関数内のみ
    ● つまり
    ○ 条件分岐の中で呼び出したり
    ○ ネストされた関数の中で呼び出したり
    → 使うHooksが一意ではない呼び出し方をしてはいけません

    View full-size slide

  16. 脆弱性を指摘できますか?

    View full-size slide

  17. vs Component(props)

    View full-size slide

  18. と Component(props) の違いは?
    ● == 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 の関数内のみ」に違反する

    View full-size slide

  19. 通常の挙動 ()

    View full-size slide

  20. 通常の挙動 ()

    View full-size slide

  21. 通常の挙動 ()
    App:useState(0)
    count: 0
    Hooks List

    View full-size slide

  22. 通常の挙動 ()
    App:useState(0)
    count: 0
    Hooks List
    EvenComponent:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  23. 通常の挙動 ()
    App:useState(0)
    count: 0
    Hooks List
    EvenComponent:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  24. 通常の挙動 ()
    App:useState(0)
    count: 0
    Hooks List
    EvenComponent:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  25. 通常の挙動 ()
    App:useState(0)
    count: 0
    Hooks List
    App:useState(0)
    count: 1
    初期化せずに再利用
    EvenComponent:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  26. 通常の挙動 ()
    App:useState(0)
    count: 1
    Hooks List
    EvenComponent:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  27. 通常の挙動 ()
    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
    異なるコンポーネントなので初期化

    View full-size slide

  28. 通常の挙動 ()
    App:useState(0)
    count: 1
    Hooks List
    OddComponent:useState("I'm Odd")
    msg: I'm Odd

    View full-size slide

  29. 通常の挙動 ()
    App:useState(0)
    count: 1
    Hooks List
    OddComponent:useState("I'm Odd")
    msg: I'm Odd

    View full-size slide

  30. 間違った実装 (Component({...}))

    View full-size slide

  31. 間違った実装 (Component({...}))
    App:useState(0)
    count: 0
    Hooks List

    View full-size slide

  32. 間違った実装 (Component({...}))
    App:useState(0)
    count: 0
    Hooks List
    App:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  33. 間違った実装 (Component({...}))
    App:useState(0)
    count: 0
    Hooks List
    App:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  34. 間違った実装 (Component({...}))
    App:useState(0)
    count: 0
    Hooks List
    App:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  35. 間違った実装 (Component({...}))
    App:useState(0)
    count: 1
    Hooks List
    App:useState("I'm Even")
    msg: I'm Even

    View full-size slide

  36. 間違った実装 (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
    初期化せずに再利用

    View full-size slide

  37. 間違った実装 (Component({...}))
    App:useState(0)
    count: 1
    Hooks List
    App:useState("I'm Odd")
    msg: I'm Even

    View full-size slide

  38. 間違った実装 (Component({...}))
    App:useState(0)
    count: 1
    Hooks List
    App:useState("I'm Odd")
    msg: I'm Even

    View full-size slide

  39. これってヤバいんですか?
    ● ヤバいです
    ● 次のようなコードを考えるとわかりやすい

    View full-size slide

  40. これってヤバいんですか?
    ● ヤバいです
    ● 次のようなコードを考えるとわかりやすい
    スプレッド構文

    オブジェクトの中身が展開される

    View full-size slide

  41. つまり
    ● コンポーネントの使い方によってはimgタグの属性をコントロール可能
    ● scale = { src: "x", onerror: "alert(1);" }


    View full-size slide

  42. このバグを整理すると
    ● 以下の条件が揃うと脆弱性となる
    ○ 攻撃者がある程度データの中身をコントロール可能な Stateがある(source)
    ○ Stateの内容によっては予期せぬ挙動を引き起こす箇所がある( sink)
    ○ 以上の2箇所のStateの取り違いが起こる箇所がある( confusion)
    ○ 取り違いが起こっても呼ばれる Hooksの数は変わらない(layout)
    source, sink, confusion, layoutという名前は一般的な用語ではありません
    ● 例
    ○ クエリ文字列をオブジェクトに変換している( source)
    ○ スプレッド構文でwidth, heightをimgタグに展開している( sink)

    View full-size slide

  43. このバグは検知可能ですか?

    View full-size slide

  44. 一応可能です
    ● Reactはすごいのであの手この手でバグる可能性があることを教えてくれる
    ○ Devモード
    ■ 呼ばれているHooksの種類が変わったら consoleを警告で真っ赤に染めてくれる
    ○ eslint
    ■ plugins:react-hooks/recommendedを足すと教えてくれる

    ● 静的にも動的にも検出する努力をしている!
    ○ 本当に十分?

    View full-size slide

  45. 実は十分ではない
    ● Reactはすごいのであの手この手でバグる可能性があることを教えてくれる?
    ○ Devモード
    ■ 呼ばれているHooksの種類が変わったら consoleを警告で真っ赤に染めてくれる
    → Hooksの種類が変わらないと教えてくれない
    ○ eslint
    ■ plugins:react-hooks/recommendedを足すと教えてくれる
    → CRA (create-react-app) や Vite (create-vite) でOptional
    → 標準の設定では検知できない

    View full-size slide

  46. 実は十分ではない
    ● すべてのチェックをすり抜けるパターン: 例で挙げたやつ

    View full-size slide

  47. なぜ十分でないのか?
    ● ESLintのルールが雑
    ○ フックが必ずReactの関数内で呼ばれているか?はチェックしてくれる
    ■ Reactの関数内か? → 関数名しかチェックしない( Camelcase or useHookName)
    ○ React.createElementとして呼び出しているか?はチェックしてくれない
    ● 検知しにくい → いろんなプロダクトに潜んでいる可能性
    ○ Reactで書かれたUIは複雑になりがちなので多分 OSSにもある
    ○ バグバウンティチャンス
    ○ これを検知するESLintとかを書いたら喜ばれるかも

    View full-size slide

  48. 問題となりやすい実装

    View full-size slide

  49. Case 1: callbackの中で呼んでいる
    ● どちらのuseStateが先に呼ばれるか決定できない
    ● 二回目のrender時に逆転する可能性
    ● eslint: 検知
    ● dev: 検知
    ● prod: 検知

    View full-size slide

  50. Case 2: 条件分岐の中で呼んでいる
    ● useState
    ● useState
    でconfusion
    ● eslint: 検知
    ● dev: 部分的に検知
    ○ Hooksの種類が
    異なる場合のみ
    ● prod: 検知不可

    View full-size slide

  51. Case 3: 関数の中から呼んでいる
    ● 例で使ったパターン
    ● eslint: 検知不可
    ● dev: 部分的に検知
    ○ Hooksの種類が
    異なる場合のみ
    ● prod: 検知不可

    View full-size slide

  52. 対策とまとめ

    View full-size slide

  53. 対策方法
    ● 基本的に発火する条件が厳しい
    ○ source, sink, confusion, layoutがすべて揃うことが必要
    ● 開発者が気をつけなければならない箇所: confusion
    ○ 他の条件は防ぎようがない
    ● 関数呼び出しは検出できないので気を付ける

    View full-size slide

  54. まとめ
    ● React Hooksのルールを守りましょう
    ○ フックを呼び出すのはトップレベルのみ
    ○ フックを呼び出すのは Reactの関数内のみ
    ● (多分)いろんなところに潜んでいる
    ○ 是非探してみてください

    View full-size slide

  55. 参考文献
    ● 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/

    View full-size slide