🏖️

RSC/Next.jsは信頼できないかもだけど、それで良いのかもしれない

に公開3件

背景

先日、RSCに重大な脆弱性が見つかりました。詳細については多くの記事が書かれていますので割愛しますが、Next.jsをただデプロイしているだけでもやられる重大さです。

RSCの設計自体も問題視されています。当初からセキュリティの議論が多く、今回の件とは直接関係がないものの、サーバとクライアントの境界が曖昧な点などが指摘されていました。この状況を見ていると最悪の場合、RSC/Next.jsのセキュリティを十分に高めることが現実的に不可能となる可能性も残されており、今後注視する必要があります。

でも本当にNext.jsってセキュリティが重要なのだろうか? サーバはセキュアでなければならないと我々はずっと学んできましたが、これが先入観である可能性もあります。

私たちが日常的にウェブサイトを作るとき、「ブラウザからのデータは絶対に信用するな!」って教わります。ブラウザ等のHTTPクライアントを、私たちは基本的には信用しません。ということは、ウェブサイトが表示されるまでの流れの中で、途中に信用できないものがあっても対応可能なのです。

本記事では 「サーバはセキュアでなければならない」というのは先入観だという視点に立ち、Next.jsをBFFとして使う場合はセキュアでなくても良い可能性を探ります。RSC/Next.jsのBFFはあくまでもブラウザの拡張であると捉え、BFFを信用しないアーキテクチャを考えます。

Next.jsのBFFを信頼できないものとして考える

Next.jsサーバを信頼した構成

ここではNext.jsサーバを信頼した構成の代表として、完全フルスタックのものについて話をします。この構成だとNext.jsは直接DBにアクセスをしますし、外部サービスにアクセスするための長期クレデンシャルも読めないといけません。

この場合はNext.jsに侵入されるとDBにアクセスされてしまいますし、外部サービスに自由にアクセスするための長期クレデンシャルも盗まれてしまいます。甚大な被害になる可能性が高いです。

Next.jsサーバを信頼しない構成

ここではNext.jsをBFFとし、Ruby on RailsをAPIサーバとした構成の話をします。 DBアクセスはAPIサーバだけが行います。また外部サービスにアクセスするための長期クレデンシャルはNext.js BFFからは読まず、APIサーバだけが長期クレデンシャルを読むようにします。

この場合、攻撃者はNext.js BFFに侵入できてもAPIサーバには侵入できず、DBにアクセスできません。外部サービスの長期クレデンシャルも盗めません。既存データは被害を受けませんので、影響範囲は限定的になります。

この構成の特徴はNext.js BFFを一切信用していないことです。ブラウザに対する一般的な姿勢と同じです。つまりNext.js BFFはあくまでもブラウザの一部であり、ハード的にはサーバではあるもののセキュリティ的にはサーバ扱いしないのです。

Next.jsを信頼しない方が他のフロントエンド技術と共存しやすい

Next.jsを信頼しない構成はセキュリティだけではなく、実は他の面でも好都合です。特に複数のフロントエンド技術が使われているプロダクトではメリットがあります。

長期間運用されている製品は、複数のフロントエンド技術を使用していることが一般的です。例えば下記のものを組み合わせていることが珍しくありません。

  • Rails ERB (+ jQuery)
  • React SPA
    • クラスコンポーネントを使うもの、Reduxを使うもの、あるいはNext.jsだがSPAとして使用しているものなど、React SPAだけでも複数のタイプが存在することがあります
  • React SSR/RSC (Next.js)

フロントエンド技術が多数存在しても、ユーザが技術の境目を意識することなくサイトをスムーズに使えることは必須です。そのため、SSR/RSCを使ったNext.jsのことだけではなく、その他の技術との共存も考える必要があります。

このケースではNext.js BFFを信頼しない設計が適しています。Rails ERBやReact SPAも信頼されておらず、Next.js BFFも信頼しない方が一貫性があるためです。

このケースではセキュリティ問題を抜きに考えても、Next.js BFFを信頼しない設計の方が良いのです。

認証の視点で信頼しない設計のメリットを考える

Next.js BFFを信頼しない設計の方が適切であることを、もう少し具体的に考えます。認証の例で考えます。

  • Next.jsを信頼する設計の場合: Next.jsに対してブラウザがcookie認証をします
    • 例えばNextAuthがよく使用されます
    • Next.jsはパスワードの確認等のためにDBや外部サービスにアクセス用のSECRETを持ちます
    • 他のSPA/ERBの認証とは異なる仕組みを使用するため、cookieの互換性がありません。これが原因で認証の共通化に苦労するケースがあります
  • Next.jsを信頼しない設計の場合: Railsに対してNext.jsがcookie認証をします
    • Next.jsはcookieをそのままブラウザにリレーします。Next.jsは中継するだけですので、SECRET等を持ちません。認証はあくまでもRuby on RailsのAPIサーバが担います。
    • BFFがリレーする点を除けば、SPA/ERBの認証と全く同じになります。Cookieもそのまま共通で使用でき、シームレスに認証を共通化できます

Next.jsを信頼しない方がフロントエンドエンジニアは楽

Next.jsのBFFは一般にフロントエンドエンジニアが管理します。AWSを使う場合は大変ですが、もちろんVercelなどを使うとかなり楽になります。

しかし大変なのはサーバの管理だけではありません。認証・認可、CSRF等々もサーバサイドコードを書く際に重要です。クレデンシャルなどの機密情報の管理にも配慮が必要です。Next.js BFFは信頼できないと決めてしまえば、フロントエンドエンジニアはこのようなことに頭を悩ませないで済みます。

それで何が言いたいの?

今回のRSC脆弱性の問題をきっかけに、Next.jsの役割を振り返り、最も適切なNext.jsの使い方を確認すると良いのではないかと思います。前から言われていたことではあるのですが、セキュリティ面ではNext.jsに不安があるからです。

数年前はNext.jsがフルスタックフレームワークとしてRailsを代替しいくのではないかと言われたことがあり、またクラウドDB等の登場により、サーバレス環境で動作するNext.jsもRDMSに直接接続できるようにもなりました。しかしNext.jsの強みはそこではなく、あくまでもフロントエンドのUI/UX及びDXを改善することに軸足があります。Next.js/Reactの近年のアップデートもRSC/Streamingやキャッシュ周りが主で、バックエンドを強化するものではありません。これはReactの歴史を考えれば当然のことです。

大型システムのフロントエンドでNext.jsを使用している場合は、もともとNext.js BFFを信頼しない構成になっているものが多いはずです。そして小規模で短命のアプリならNext.jsを信頼して完全フルスタックにしても良いでしょう。

ただし長期間運用し、機密性の高い情報を載せ、多くの機能を追加していくようなシステムの場合はNext.jsのフルスタックは適していないと考えた方が良さそうです。

RSCに重大な脆弱性が見つかったからNext.jsの使用を諦めるのではなく、ネットに落ちているフルスタックNext.jsの記事等に惑わされず、自分の用途に合わせた適切な使い方をすれば良いだけのことだと思います。

Discussion

rinsukirinsuki

Next.js のサーバーインスタンスが複数リクエストで使い回される一部の環境[1]では、RCE ができた時点でそのインスタンスが返す他ユーザーへのリクエスト・レスポンスが書き換えられる可能性があるため、たまたま攻撃者と同じインスタンスでリクエストが処理されたユーザーのCookieの奪取や、サービス内の全ページでXSSができる程度の影響力が (BFFをバックエンドがブラウザと同等に一切信頼していなかったとしても) 依然としてありうると思います。

(今回の脆弱性で他ユーザーへのレスポンスを書き換えている実際の例: https://forest.watch.impress.co.jp/docs/serial/yajiuma/2072085.html )

また、仮にサーバーインスタンスが毎リクエスト捨てられる環境であっても、

  • バックエンドへの X-Forwarded-For をちょろまかしてIPアドレスベースのレートリミットを迂回しバックエンドを叩きまくる (サイト固有テクなのでよほどでかいサイトでない限りは目を付けられなさそうですが)
  • 外部通信が上位レイヤーで制限されていない場合、踏み台にして他所に迷惑をかけまくる

など様々な悪用方法が考えられます。

多層防御の一層として、BFFは信頼しないべきだと思いますし、データベースなどへの直の接続権限を渡している場合と比較すれば有事があった際の被害は明らかに抑えられると思いますが (影響を受けるユーザーが"全ユーザー"から"一定期間の間にアクセスしていたユーザー"程度まで減らせる)、依然として Next.js (というよりBFF) のサーバーはセキュアに保つ必要があると考えますし、セキュアでないと考えるのならそのようなものは置かないべきだと考えます。

脚注
  1. 最近あまりサーバーレスの類を使っておらず自信がないのですが、本物の Node.js が動く環境は恐らくだいたいの場合1サーバーが複数リクエストで使われ、独自ランタイムな Edge Functions の類は毎回捨てられるはず。 ↩︎

8
NaofumiNaofumi

BFFにDB接続権限や長期クレデンシャルを渡していなかったとしても問題があるのではないかというコメントを上記でいただきました。またXのポストでもいただきました。

これについては本記事を書くときも少し調査を行ったのですが、中に含めるほどには整理できていませんでした。ここでは簡単に私の考えを紹介します。

  1. 指摘された通り、通常のサーバの場合はDB接続権限や長期クレデンシャルを持たせていなかったとしても、RCE (サーバで任意のコードを実行)できてしまう時点で十分に大きな被害を受ける可能性があります
  2. 一方でVercel、Cloudflare、AmplifyやNetlifyなどにデプロイした場合は、Next.jsはFaaS上で実行されます
  3. FaaSの場合は以下の理由により、サーバに侵入された場合の被害が少なくなります
    • FaaSはリクエストごとに環境が隔離されるため(絶対ではないにしても)、RCEがログインしているユーザのトークンなどを盗める可能性が減ります
    • FaaSはCPU timeが数十秒もしくは数分使用されると自動的にkillされます。そのため侵入されたサーバでBitcoinマイニングするのに十分な時間が与えられません
    • FaaSはかなりマネージされているので、異常な活動があればシャットダウンされる(ことが多いらしい)

BFFを信頼しないということは、なんらかのsandboxの中で実行しなければならないということです。FaaSにデプロイしていれば(完全ではないにしても)一定の効果があるsandboxが用意されています。これはやはり我々が使用しているブラウザと同じで、少なくとも15年ほど前からセキュリティを強化するためにタブやiframeごとにsandboxするのが一般的です。

一方で通常のNodeサーバとしてデプロイしているケースではsandboxが不十分になります。よってBFFにDB接続権限や長期クレデンシャルを渡していなかったとしても、RCEされてしまえば大きな被害が出る可能性があります。こういったケースではより注意を払うべきでしょう。

BFFは信頼できないという考え方は、私の知る限りでは全然一般的ではありません。そのため積極的にBFFをsandbox上で走らせる話も聞きませんが、いずれはそういう考え方が広まるかもしれません。

2