Next.js Server Actions mendedahkan setiap fungsi yang dieksport dalam fail use-server secara automatik sebagai titik akhir (endpoint) HTTP awam, dengan menetapkan pengenal pasti unik yang digunakan oleh klien untuk memanggil fungsi tersebut. Titik akhir tersebut wujud sama ada butang, pautan, atau sebarang elemen UI lain dipaparkan atau tidak, bermakna sesiapa sahaja yang menemui pengenal pasti tersebut boleh memanggil fungsi itu secara terus.
Reka bentuk rangka kerja ini melayan Server Actions seperti fungsi pembantu (helper functions) biasa, tetapi semasa masa larian (runtime), ia menjadi URL yang boleh dicapai. Bagi pembangun yang menganggap UI sebagai satu-satunya pengawal pintu, ini mewujudkan permukaan serangan (attack surface) yang tidak kelihatan yang boleh dieksploitasi dengan satu permintaan yang direka khas.
Mengapa Server Actions kelihatan selamat
Server Actions diperkenalkan untuk membolehkan pembangun menulis kod bahagian pelayan (server-side) bersebelahan dengan komponen mereka, bagi mengelakkan kod lewah (boilerplate) bagi laluan API yang berasingan. Penggunaan tipikal adalah seperti:
<form action={deleteInvoice}>
<button type="submit">Delete</button>
</form>
Oleh kerana borang tersebut adalah satu-satunya cara yang kelihatan untuk mencetuskan deleteInvoice, ramai pembangun menyembunyikan butang untuk pengguna yang tidak dibenarkan, menyangka bahawa tandatangan TypeScript akan menghalang data yang tidak sah, dan bergantung pada fakta bahawa fungsi tersebut berada dalam modul khusus pelayan (server-only module). Tiada satu pun daripada andaian tersebut yang memberikan perlindungan sebenar.
Pendedahan tersembunyi
Apabila sesebuah fail mengandungi “use server”, Next.js menyusun (compile) setiap fungsi yang dieksport menjadi titik akhir seperti:
POST /_next/data/<build-id>/<page>.json?__rsc=<action-id>
<action-id> ialah hash stabil yang disematkan dalam bundle klien. Penyerang boleh mendapatkannya dengan:
- Memeriksa trafik rangkaian halaman tersebut.
- Membaca JavaScript yang dibundle (ID tersebut adalah rentetan teks biasa).
- Meneka berdasarkan konvensi penamaan jika projek tersebut mengikut corak yang boleh diramal.
Sebaik sahaja ID diketahui, permintaan boleh dihantar daripada mana-mana alatan—cURL, Postman, atau skrip berniat jahat—dengan memintas sebarang semakan pada tahap UI.
Tiga risiko nyata
| Risiko | Mengapa ia penting |
|---|---|
| Semakan UI tidak berkesan | Menyembunyikan butang atau pautan tidak memadamkan titik akhir yang mendasarinya. Titik akhir tersebut tetap boleh dicapai, sama seperti halaman admin tersembunyi yang masih wujud pada pelayan. |
| TypeScript tidak menawarkan keselamatan masa larian | Jenis (Types) dibuang apabila kod dijalankan. Fungsi yang diisytiharkan sebagai deleteInvoice(id: number) boleh menerima rentetan yang sangat besar, tatasusunan (array), atau pun JSON berniat jahat, yang membawa kepada ralat logik atau serangan suntikan (injection attacks). |
| Tiada pengesahan atau kebenaran lalai | Server Actions kelihatan seperti pembantu tempatan, jadi pembangun sering terlupa untuk menambah semakan sesi, perlindungan CSRF, atau semakan kebenaran peringkat baris (row-level) yang merupakan standard dalam laluan API tradisional. |
Cara mengamankan Server Action
- Sahkan pemanggil – Sahkan bahawa sesi atau token yang sah wujud sebelum sebarang logik perniagaan dijalankan.
- Sahkan muatan (payload) – Gunakan perpustakaan skema (contohnya, Zod, Yup) untuk menguatkuasakan jenis data dan kekangan nilai semasa masa larian.
- Berikan kebenaran operasi – Selain daripada "adakah pengguna telah log masuk?", sahkan bahawa pengguna tersebut memiliki rekod khusus yang cuba mereka ubah atau padam.
Contoh minimal:
'use server';
import { getSession } from '@/auth';
import { z } from 'zod';
import { db } from '@/db';
const DeleteInvoiceSchema = z.object({
id: z.number().int().positive(),
});
export async function deleteInvoice(formData: FormData) {
const session = await getSession();
if (!session) throw new Error('Unauthenticated');
const parsed = DeleteInvoiceSchema.safeParse({
id: Number(formData.get('id')),
});
if (!parsed.success) throw new Error('Invalid input');
const invoice = await db.invoice.findUnique({ where: { id: parsed.data.id } });
if (!invoice || invoice.ownerId !== session.userId) {
throw new Error('Unauthorized');
}
await db.invoice.delete({ where: { id: invoice.id } });
}
Kod tersebut menyemak pengesahan secara eksplisit, mengesahkan id yang masuk, dan memastikan pengguna yang log masuk benar-benar memiliki invois tersebut sebelum melakukan pemadaman.
Perangkap Next.js lain yang halus
| Isu | Simptom | Penyelesaian |
|---|---|---|
Pemboleh ubah persekitaran NEXT_PUBLIC_ |
Apa-apa sahaja yang bermula dengan NEXT_PUBLIC_ akan dibundle ke dalam klien, mendedahkan rahsia. |
Simpan rahsia dalam pemboleh ubah persekitaran biasa, jangan sesekali meletakkan awalan NEXT_PUBLIC_ padanya. |
| Lencongan terbuka (Open redirects) | Menerima parameter pertanyaan redirect dan menyambungkannya secara naif boleh menghantar pengguna ke //evil.com. |
Sahkan sasaran terhadap senarai putih (whitelist) atau kuatkuasakan semakan asal-sama (same-origin). |
| Pemalsuan Permintaan Pihak Pelayan (SSRF) | Mengambil URL yang dibekalkan oleh pengguna boleh membolehkan penyerang mencapai perkhidmatan dalaman atau titik akhir metadata awan. | Senarai putih (Allow-list) nama hos, sekat julat IP peribadi, dan tetapkan masa tamat (timeouts). |
dangerouslySetInnerHTML |
Memaparkan HTML yang disediakan oleh pengguna tanpa sanitasi membuka ruang kepada XSS. | Gunakan perpustakaan seperti DOMPurify atau elakkan penggunaan HTML mentah sepenuhnya. |
Mengimbas corak ini secara automatik
Alatan analisis statik boleh menandakan binaan berisiko yang disenaraikan di atas. Satu pilihan ringan ialah Semgrep, yang berjalan dengan pantas dan boleh disepadukan ke dalam saluran paip (pipeline) CI:
npx --yes semgrep --config https://raw.githubusercontent.com/catidegla/stacksec/main/rules .
Set peraturan tersebut merangkumi semakan untuk fungsi pelayan yang dieksport, penyalahgunaan NEXT_PUBLIC_, lencongan terbuka, corak SSRF, dan penyisipan HTML yang tidak selamat.
Apa yang perlu diperhatikan seterusnya
- Kemas kini rangka kerja – Pantau keluaran Next.js; pasukan mungkin memperkenalkan hook pengesahan terbina dalam atau mengasingkan titik akhir (endpoint) yang dijana dalam sandbox.
- Peralatan komuniti – Plugin ESLint baharu dan peraturan Semgrep khusus untuk Next.js sedang muncul untuk mengautomasikan langkah perlindungan yang diterangkan di sini.
- Insiden dunia nyata – Memandangkan lebih banyak projek menggunakan Server Actions, perhatikan eksploitasi yang didedahkan yang menggambarkan risiko dalam praktis. Pengesanan awal boleh membantu semakan keselamatan dalaman sebelum berlaku pelanggaran.
Rumusan: Next.js Server Action bukanlah fungsi pembantu peribadi; ia merupakan titik akhir (endpoint) HTTP awam sebaik sahaja anda mengeksportnya. Layan ia seperti mana-mana laluan API yang lain—lakukan pengesahan identiti, pengesahan data, dan pemberian kuasa—jika tidak, kemudahan menulis kod pelayan bersebelahan dengan UI boleh dengan cepat menjadi liabiliti keselamatan.
