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
Next.js セキュリティ 気をつけないと情報漏洩している
Search
mu_zaru
October 16, 2025
Programming
0
18
Next.js セキュリティ 気をつけないと情報漏洩している
Next.js App Router のセキュリティで気をつけたい話をします
mu_zaru
October 16, 2025
Tweet
Share
More Decks by mu_zaru
See All by mu_zaru
非公開 URL は安全か?あるいは、そのランダム文字列は本当にランダムなのか?
mu_zaru
0
8
CMD について語りたい : その Dockerfile、30 秒無駄にしてるかも?
mu_zaru
1
46
一緒に働きたくなるプログラマの思想 #QiitaConference
mu_zaru
87
23k
知ると楽しくなるプログラミングの原理原則
mu_zaru
12
4.9k
Ajax 再々再入門
mu_zaru
3
620
CSS 余白をマスターする margin/padding 編
mu_zaru
1
1.1k
JS 入門 async/await について
mu_zaru
1
1.3k
JavaScript 今ドキな書き方 ES2020
mu_zaru
16
12k
サクッと立ち上がる環境構築 docker-compose 入門
mu_zaru
2
1.4k
Other Decks in Programming
See All in Programming
Server Side Kotlin Meetup vol.16: 内部動作を理解して ハイパフォーマンスなサーバサイド Kotlin アプリケーションを書こう
ternbusty
3
180
オープンソースソフトウェアへの解像度🔬
utam0k
15
2.8k
CSC305 Lecture 06
javiergs
PRO
0
230
はじめてのDSPy - 言語モデルを『プロンプト』ではなく『プログラミング』するための仕組み
masahiro_nishimi
2
380
iOSエンジニア向けの英語学習アプリを作る!
yukawashouhei
0
200
Range on Rails ―「多重範囲型」という新たな選択肢が、複雑ロジックを劇的にシンプルにしたワケ
rizap_tech
0
130
いま中途半端なSwift 6対応をするより、Default ActorやApproachable Concurrencyを有効にしてからでいいんじゃない?
yimajo
2
420
Go言語の特性を活かした公式MCP SDKの設計
hond0413
1
230
私はどうやって技術力を上げたのか
yusukebe
43
18k
TFLintカスタムプラグインで始める Terraformコード品質管理
bells17
2
170
開発生産性を上げるための生成AI活用術
starfish719
3
950
階層構造を表現するデータ構造とリファクタリング 〜1年で10倍成長したプロダクトの変化と課題〜
yuhisatoxxx
3
1k
Featured
See All Featured
The Pragmatic Product Professional
lauravandoore
36
6.9k
Reflections from 52 weeks, 52 projects
jeffersonlam
353
21k
How to train your dragon (web standard)
notwaldorf
97
6.3k
A Modern Web Designer's Workflow
chriscoyier
697
190k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
Testing 201, or: Great Expectations
jmmastey
45
7.7k
4 Signs Your Business is Dying
shpigford
185
22k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
252
21k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.7k
Build your cross-platform service in a week with App Engine
jlugia
232
18k
GraphQLとの向き合い方2022年版
quramy
49
14k
Side Projects
sachag
455
43k
Transcript
Next.js セキュリティ #Offers_DeepDive 2024.11.06 @zaru / nanabit
自己紹介 @zaru ムーザルちゃんねるから来ました nanabit という会社で開発支援してます
今日は Next.js App Router の セキュリティで気をつけたい話をします
Next.js App Router の簡単な紹介
Server Component Streaming with Suspense Server Actions 主に React19 の新機能を先取りして提供
Server Component Streaming with Suspense Server Actions 今日は主にこの 2 つに関連する話
Server (Component|Actions) is 何? フロントエンドとバックエンドの境界線をなくす コンポーネントから直接 SQL 実行できる フロントの状態を極力持たずに書ける バックエンドの処理を関数で呼び出せる
None
じゃあ、 雰囲気でなんとなーく理解したところで、 本題に入りましょう。 今日はちょっとしたゲームを Next.js で作ってきたので、 それで遊びます。 このゲームには脆弱性がいくつかあります。 簡単なので調査してみてね。 (*)
DoS とかしちゃだめだよ。 そういうのじゃないよ。
Next Clicker 今すぐアクセスして脆弱性を見つけよう
https://nextclicker.nanabit.dev/ 今日の解説に使う題材です
見つかりましたか? では答えです
無防備な Server Actions
Ⓝ をクリックすると、 通信が発生している
Request Header で Next-Action を送信してる curl で再現するとこんな感じの POST 通信 curl
'https://nextclicker.nanabit.dev/' \ -H 'Content-Type: text/plain;charset=UTF-8' \ -H 'Cookie: ...' \ -H 'Next-Action: 6eb8416051487420c0347306825a392adf55f29e' \ --data-raw '[1]' 手動でリクエストを試してみる
--data-raw '[9999]' の数値をいじると、 増やす数値を変更可 能なことが分かった curl 'https://nextclicker.nanabit.dev/' \ -H 'Content-Type:
text/plain;charset=UTF-8' \ -H 'Cookie: ...' \ -H 'Next-Action: 6eb8416051487420c0347306825a392adf55f29e' \ --data-raw '[9999]' # これで9999増える 任意の数値でスコアを挙げられることを発見
"use server"; export async function incrementalScore(power: number) { const user
= await currentUser(); if (!user) return; await prisma.user.update({ where: { id: user.id }, data: { score: { increment: power } }, }); revalidatePath("/"); } Bad: 引数で上げる数値を受け取り信頼してしまっている Server Component 関数の実装
Server Actions = HTTP エンドポイント 見た目は関数を呼び出しているが HTTP エンドポイント 自動でエンドポイントをたて、 内部で
fetch している つまり外部に公開されているので引数は信頼できない 引数は必ずパースし、 必要なら認証もする 外部公開 API と捉えて設計と実装をすること こう考える
露出してる Server Actions
DevTools の Search で [0-9a-z]{40} で検索する Request Header で Next-Action
で使う ID が見つかる Server Actions の探し方
curl 'https://nextclicker.nanabit.dev/' \ -H 'Content-Type: text/plain;charset=UTF-8' \ -H 'Next-Action: 8d2238ce9fde355707d6a4b613a12f5d5360427c'
\ --data-raw '[1]' 0:["$@1",["EBYDKshKtbxTnpEuKdpC4",null]] 1:{"id":1,"name":"zaru","password":"$$2b$10$k6BnP5...","score":280,"level":0} ハッシュ化されたパスワードが返ってきた・ ・ ・! 適当にリクエストしてみる
"use server"; は Server Actions にするディレクティブ 名前から 「サーバ処理をする時に付けるもの」 と勘違い 盲目的に、
DB を叩く関数すべてにつけてしまった・ ・ ・ "use server"; # 内部でしか使ってないのに、意図せず外部公開されてしまった関数 export async function fetchRankers() { return prisma.user.findMany({ orderBy: { score: "desc" }, take: 10, }); } 意図せず Server Actions になってしまった例
"use server"; != サーバ処理 上記を理解していても 1 ファイルに複数の関数を export して いると、
気が付かずに ServerActions になってしまうものが出 てくる可能性 v15 ではID が露出しないように改善された Knip というツールで未 export 関数の検出可能 こう考える
Client Component に漏れる
DevTool を使ってユーザ名を検索・ ・ ・ めっちゃパスワードがブラウザ側に露出している 情報漏洩してないか検索
Server Component で取得したユーザ情報をそのまま Client Component に渡している // RankingはServer Component export
async function Ranking() { const users = await fetchRankers(); return ( {users.map((user) => ( // RankingItemはClientComponent <RankingItem key={user.id} user={user} /> ))} ); } Server と Client の境界線
コンポーネント内部で使っているかどうかは関係ない Server Component は JSX に使っているものだけ露出する 面倒くさがってオブジェクト全部渡しちゃったり・ ・ ・ 構造的片付けで意図しないプロパティが含まれてしまうケ
ースも ClientComponent の props は全て露出
必要な情報だけ props に渡す コンポーネントが Server か Client かで考えると境界線意識 がつらい どんなコンポーネントでも必要な情報だけ扱うようにする
TaintAPI や Pick 型/Zod パースなどを使う それぞれ効能が少し異なるのでシーンによって使い分ける こう考える
SQL なら原則 SELECT 句は指定した方が良い export async function fetchRankers() { return
prisma.user.findMany({ // Prismaの例 select: { id: true, name: true, score: true, level: true }, }); } SELECT 句を明示的にする
React の提供する Taint API を使うと使用禁止をマークできる サーバ側でしか使わないようなオブジェクトに対して利用可能 ただし、 実行時エラーなのでエディタ上では分からない export async
function fetchRankers() { const users = await prisma.user.findMany(); for (const user of users) { // userオブジェクトをClient Componentに渡すとエラーになる taintObjectReference("Client Componentでは使えません", user); } return users; } Taint API を使う
ライブラリでパースし、 必要ないプロパティを落とす const schema = z.object({ id: z.number(), name: z.string(),
}); // パースすると、id / name以外のプロパティは消える const parsed = schema.safeParse(user); Zod でパースする
認証してるのに見える
None
条件分岐で弾かれているのにコードに含まれちゃってる またも DevTool で検索する
None
Layout で認証しない v15 では Layout からレンダリングされるので回避は可能 ただし順序に依存すると Next.js の変更で露出するかも 隠したい情報を表示するコンポーネント自体で判定させる
将来的には Request Interceptors で共通処理できるかも https://github.com/vercel/next.js/pull/70961 こう考える
仕組みを理解して使うことが大事 フロントとバックの境界線を意識する この先、 境界線をまたぐ書き方は増えていく気がする 自分の領域を決めすぎず広く対応していきたい
import 'server-only'; で Client Component に含ませない next-safe-action という Server Actions
を少し安全にする https://next-safe-action.dev/ 似たようなライブラリは他にもいくつかある おまけ
おしまい もし Web アプリの開発・技術顧問、 エンジニア組織・文化の相談あれば、 @zaru までお問い合わせください