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
53
Next.js セキュリティ 気をつけないと情報漏洩している
Next.js App Router のセキュリティで気をつけたい話をします
ムーザルちゃんねる
October 16, 2025
Tweet
Share
More Decks by ムーザルちゃんねる
See All by ムーザルちゃんねる
非公開 URL は安全か?あるいは、そのランダム文字列は本当にランダムなのか?
mu_zaru
0
39
CMD について語りたい : その Dockerfile、30 秒無駄にしてるかも?
mu_zaru
1
50
一緒に働きたくなるプログラマの思想 #QiitaConference
mu_zaru
88
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.4k
JavaScript 今ドキな書き方 ES2020
mu_zaru
16
12k
サクッと立ち上がる環境構築 docker-compose 入門
mu_zaru
2
1.4k
Other Decks in Programming
See All in Programming
三者三様 宣言的UI
kkagurazaka
0
330
Inside of Swift Export
giginet
PRO
1
310
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
670
品質ワークショップをやってみた
nealle
0
900
data-viz-talk-cz-2025
lcolladotor
0
110
Amazon ECS Managed Instances が リリースされた!キャッチアップしよう!! / Let's catch up Amazon ECS Managed Instances
cocoeyes02
0
120
Towards Transactional Buffering of CDC Events @ Flink Forward 2025 Barcelona Spain
hpgrahsl
0
120
ビルドプロセスをデバッグしよう!
yt8492
0
210
CSC509 Lecture 10
javiergs
PRO
0
160
CSC305 Lecture 12
javiergs
PRO
0
250
Register is more than clipboard
satorunooshie
1
340
Making Angular Apps Smarter with Generative AI: Local and Offline-capable
christianliebel
PRO
0
100
Featured
See All Featured
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
34
2.3k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
Why You Should Never Use an ORM
jnunemaker
PRO
60
9.6k
4 Signs Your Business is Dying
shpigford
186
22k
KATA
mclloyd
PRO
32
15k
Large-scale JavaScript Application Architecture
addyosmani
514
110k
The Invisible Side of Design
smashingmag
302
51k
Facilitating Awesome Meetings
lara
57
6.6k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
127
54k
Producing Creativity
orderedlist
PRO
348
40k
Side Projects
sachag
455
43k
Scaling GitHub
holman
463
140k
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 までお問い合わせください