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
ムーザルちゃんねる
October 16, 2025
Programming
0
79
Next.js セキュリティ 気をつけないと情報漏洩している
Next.js App Router のセキュリティで気をつけたい話をします
ムーザルちゃんねる
October 16, 2025
Tweet
Share
More Decks by ムーザルちゃんねる
See All by ムーザルちゃんねる
非公開 URL は安全か?あるいは、そのランダム文字列は本当にランダムなのか?
mu_zaru
0
57
CMD について語りたい : その Dockerfile、30 秒無駄にしてるかも?
mu_zaru
1
65
一緒に働きたくなるプログラマの思想 #QiitaConference
mu_zaru
88
24k
知ると楽しくなるプログラミングの原理原則
mu_zaru
12
5k
Ajax 再々再入門
mu_zaru
3
630
CSS 余白をマスターする margin/padding 編
mu_zaru
1
1.2k
JS 入門 async/await について
mu_zaru
1
1.4k
JavaScript 今ドキな書き方 ES2020
mu_zaru
16
12k
サクッと立ち上がる環境構築 docker-compose 入門
mu_zaru
2
1.4k
Other Decks in Programming
See All in Programming
ローカルLLMを⽤いてコード補完を⾏う VSCode拡張機能を作ってみた
nearme_tech
PRO
0
220
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
430
The Art of Re-Architecture - Droidcon India 2025
siddroid
0
150
【卒業研究】会話ログ分析によるユーザーごとの関心に応じた話題提案手法
momok47
0
150
Tinkerbellから学ぶ、Podで DHCPをリッスンする手法
tomokon
0
150
マスタデータ問題、マイクロサービスでどう解くか
kts
0
170
Patterns of Patterns
denyspoltorak
0
400
Findy AI+の開発、運用におけるMCP活用事例
starfish719
0
2k
AtCoder Conference 2025
shindannin
0
850
公共交通オープンデータ × モバイルUX 複雑な運行情報を 『直感』に変換する技術
tinykitten
PRO
0
180
Giselleで作るAI QAアシスタント 〜 Pull Requestレビューに継続的QAを
codenote
0
330
ZJIT: The Ruby 4 JIT Compiler / Ruby Release 30th Anniversary Party
k0kubun
1
300
Featured
See All Featured
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
2.8k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
1
210
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Optimizing for Happiness
mojombo
379
70k
YesSQL, Process and Tooling at Scale
rocio
174
15k
Building Adaptive Systems
keathley
44
2.9k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
196
70k
Into the Great Unknown - MozCon
thekraken
40
2.2k
Building Flexible Design Systems
yeseniaperezcruz
330
39k
Ethics towards AI in product and experience design
skipperchong
1
150
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
590
Groundhog Day: Seeking Process in Gaming for Health
codingconduct
0
69
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 までお問い合わせください