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
110
0
Share
Next.js セキュリティ 気をつけないと情報漏洩している
Next.js App Router のセキュリティで気をつけたい話をします
ムーザルちゃんねる
October 16, 2025
More Decks by ムーザルちゃんねる
See All by ムーザルちゃんねる
非公開 URL は安全か?あるいは、そのランダム文字列は本当にランダムなのか?
mu_zaru
0
86
CMD について語りたい : その Dockerfile、30 秒無駄にしてるかも?
mu_zaru
1
96
一緒に働きたくなるプログラマの思想 #QiitaConference
mu_zaru
88
24k
知ると楽しくなるプログラミングの原理原則
mu_zaru
12
5.1k
Ajax 再々再入門
mu_zaru
3
670
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.5k
Other Decks in Programming
See All in Programming
プロパティの順序で型推論が壊れる!? TypeScript6.0の修正からContext-Sensitivityの仕組みを追う
bicstone
2
1.1k
Skillは並べた。動かなかった。契約で繋いだ。— 65個のSkillから、自走する開発サイクルへ
junholee
0
760
密結合なバックエンドから TypeScript のコードを生成する
kemuridama
1
360
New "Type" system on PicoRuby
pocke
1
190
権限チェックの一貫性を型で守る TypeScript による多層防御
mnch
4
650
ユニットテストの先へ:テスト技法で要求・仕様を整理するJava開発実践 / Beyond_Unit_Testing_Practical_Java_Development_Techniques_for_Organizing_Requirements_and_Specifications
shimashima35
0
220
ビジネスモデルから紐解く、AI+型駆動開発
hirokiomote
2
2.6k
Kubernetesを使わない環境にもCloud Nativeなデプロイを実現する / Enabling Cloud Native deployments without the complexity of Kubernetes
linyows
3
570
AIエージェントの隔離技術の徹底比較
kawayu
0
430
GitHub Copilot CLIのいいところ
htkym
2
1.1k
OCRを使ってゲームのアイテムをデータ化する
kishikawakatsumi
0
120
AlarmKitで明後日起きれるアラームアプリを作る
trickart
0
150
Featured
See All Featured
So, you think you're a good person
axbom
PRO
2
2k
Evolving SEO for Evolving Search Engines
ryanjones
0
200
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.3k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
360
Exploring anti-patterns in Rails
aemeredith
3
370
Being A Developer After 40
akosma
91
590k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
GraphQLとの向き合い方2022年版
quramy
50
15k
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
710
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Fashionably flexible responsive web design (full day workshop)
malarkey
408
66k
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 までお問い合わせください