در این مقاله، ما قصد داریم به حملات Cross-Site Request Forgery (CSRF) در یک برنامه Next.js و نحوه محافظت از خود در برابر آنها نگاهی بیندازیم. ابتدا، مفهوم حملات CSRF و اینکه چگونه میتوانند بر یک برنامه وب به طور کلی تأثیر بگذارند را بررسی خواهیم کرد. برای انجام این کار، سناریویی را شرح خواهیم داد که در آن یک حمله CSRF را به برنامه Next.js خود راه اندازی می کنیم. و از پکیج next-csrf و یک سری نکات امنیتی کوکی خاصی برای نشان دادن نحوه محافظت در برابر این حملات استفاده خواهیم کرد. با ما همراه باشید تا با همدیگر بررسی کنیم.
حمله CSRF چیست؟
تصور کنید یک سایت آنلاین بانکداری دارید و به این سایت وارد شده اید، که یک کوکی در مرورگر شما تنظیم می کند تا سشن(session) شخصی شما را حفظ کند. این کوکی حاوی یک نشانه احراز هویت است که برای شناسایی سشن شما و احراز هویت درخواست های شما استفاده می شود.
خب بیاید یک درخواست HTTP بسیار ساده درباره ارسال پول را یک نگاهی به آن بیندازیم که مثل زیر خواهد بود:
POST /transfer HTTP/1.1
Host: vulnerable-bank.com
Content-Type: application/json
Content-Length: 30
Cookie: session=454544
amount=1000$
name=friendlyuser@gmail.com
iban=DE7823778237873
در همین حین که دارید پول را ارسال می کنید ، در یک حمله CSRF، یک تب جدید دیگر در مرورگر خود دارید که یک وب سایت مخرب بالا آماده است. این وبسایت مخرب میتواند حاوی یک فرم مخفی یا کد جاوا اسکریپت باشد که با استفاده از کد احراز هویت ما، درخواستی را به سایت بانکداری آنلاین ارسال میکند.
از آنجایی که درخواست از یک مرورگر آغاز می شود، برنامه وب ما نمی تواند بین درخواست قانونی که توسط ما آغاز شده یا درخواست جعلی ارسال شده توسط مهاجم تمایز قائل شود. برنامه وب درخواست را پردازش می کند و اقدام ناخواسته را بدون اطلاع یا رضایت ما انجام می دهد. و این یک حمله CSRF می باشد.
این حملات فرم ها و درخواست هایی را از طریق یک کاربر معمولی به یک وب سایت دیگر ارسال می کند با این کار، مهاجم می تواند دسترسی نامحدود به اطلاعات کاربری را بدست آورد و یا اقدام به انجام عملیات ناخواسته مانند تغییر رمز عبور، خرید محصولات و غیره کند. برای محافظت از وب سایت خود در برابر حملات CSRF، می توانید از روش هایی مانند استفاده از توکن CSRF و بررسی Referer Header در درخواست ها استفاده کنید
چگونه از حمله CSRF محافظت کنیم
در این بخش، ما چند روش مختلف برای محافظت در برابر حملات CSRF را مورد بحث قرار خواهیم داد. و به تک تک آن ها می پردازیم.
استفاده از کوکی های SameSite
یک راه ممکن برای محافظت از برنامه Next.js در برابر حملات CSRF این است که مقدار SameSite را در داخل کوکی هایی که در وب سایت خود استفاده می کنید، تعریف کنید. گوگل این محصول را در سال 2006 با هدف جلوگیری از ارسال خودکار کوکیهای همراه با درخواستهای بین سایتی توسط مرورگر، همانطور که قبلاً انجام میداد، معرفی کرد، که خطر از بین رفتن اطلاعات محرمانه را به حداقل میرساند و در برابر جعل درخواستهای متقابل محافظت میکند.
مشخصه SameSite می تواند به عنوان مقدار strict
یا lax
در نظر گرفته شود. در حالت strict
، کوکی محافظت شده و هیچ درخواست بین سایتی ارسال نمی شود. این قبلاً برای کلیک کردن روی یک لینک ساده صدق میکند، اما وقتی برای مثال بانکداری آنلاین ما اعمال میشود، به این معنی است که همیشه باید هر بار که به صفحه بانکداری آنلاین هدایت میشوید، احراز هویت خود را مجدداً تأیید کنید.
این با رفتار معمول برنامه های وب مطابقت ندارد زیرا کاربران نمی خواهند دائماً دوباره لاگین شوند. خوشبختانه، حالت lax
این رفتار را تا حدودی بهبود می بخشد و اجازه میدهد کوکی به همراه برخی از درخواستهای بینسایتی «ایمن» ارسال شود. این فقط بر روشهای HTTP ایمن و only-read و top-level navigation تأثیر میگذارد (اعمالی که باعث میشود URL در نوار آدرس مرورگر تغییر کند، مانند لینک ها).
استفاده از کوکی های HTTP-only
با تنظیم بخش کوکی HttpOnly
، می توانید احتمال حمله CSRF را کاهش دهید زیرا کوکی های HTTP-only نمی توانند توسط جاوا اسکریپت از طریق اسکریپت های سمت کلاینت بازیابی شوند.
res.setHeader("Set-Cookie", `session=${sessionId}; Path=/; Max-Age=600; SameSite=Strict; HttpOnly`);
استفاده از توکن های CSRF
یکی از راه های محافظت از برنامه وب خود در برابر حمله CSRF استفاده از توکن های CSRF است. توکن CSRF یک مقدار تصادفی یونیک است که در سمت سرور تولید می شود و در هر درخواست ارسال شده توسط کلاینت آن هم ارسال می شود. اگر توکن ارسال شده توسط کلاینت با توکن ذخیره شده در سمت سرور مطابقت داشته باشد، درخواست قانونی تلقی می شود و توسط سرور پردازش می شود. در غیر این صورت درخواست رد خواهد شد. و با error 419 مواجهه خواهید شد.
توجه به این نکته حائز اهمیت است که توکنهای CSRF تا زمانی که توکن بهطور تصادفی تولید شود و به راحتی قابل حدس زدن یا پیشبینی نباشد، دفاع مؤثری در برابر حملات CSRF ارائه میکنند. علاوه بر این، توکن باید پس از یک دوره زمانی معین یا پس از یک بار استفاده منقضی شود تا مهاجمان از استفاده مجدد از توکنهای قدیمی جلوگیری کنند.
نحوه انجام حملات CSRF بر روی صفحات وب محافظت نشده
در این بخش، ما قصد داریم به کد نمونه صفحه بانکداری آنلاین و آسیب پذیری آن در برابر حملات CSRF نگاهی بیندازیم. پس از آن، ما میخواهیم حفاظت CSRF را با استفاده از پکیج next-csrf و تنظیم مقدار SameSite
در کوکی سشن خود پیادهسازی کنیم.
بانک آنلاین آزمایشی ما از دو روت اصلی تشکیل شده است: روت لاگین و روت انتقال. روت انتقال تنها پس از احراز هویت موفقیت آمیز از طریق روت لاگین قابل دسترسی است. برای این منظور، یک روت API ساده برای رسیدگی به درخواست لاگین ایجاد می کنیم:
// pages/api/login.js
export default function login(req, res) {
// check the user's credentials
const { username, password } = req.body;
let authenticated;
if (username === "test" && password === "123456") {
authenticated === true
} else {
authenticated === false
}
if (authenticated) {
// set a cookie with the a random sessionId
const sessionId = 454544;
res.setHeader("Set-Cookie", `session=${sessionId}; Path=/; Max-Age=600`);
// send a success response
res.status(200).json({ message: "Login successful" });
} else {
// send an error response
res.status(401).json({ message: "Invalid credentials" });
}
}
مهمترین خط کد در کدهای بالا این است:
res.setHeader("Set-Cookie", `session=${sessionId}; Path=/; Max-Age=600`);
این یک کوکی با شناسه سشن و مدت زمان 10 دقیقه تنظیم می کند. برای سادگی، از id های سشن، نامهای کاربری و رمزهای عبور رمزگذاریشده استفاده میکنیم.
پس از احراز هویت موفقیتآمیز، باید صفحه انتقال بانکداری آنلاین نسخه آزمایشی ما را مشاهده کنید.
روت API ساده شده مربوطه برای رسیدگی به نقل و انتقالات بانکی به صورت زیر است:
// pages/api/transfer.js
export default function handler(req, res) {
// Check that the request method is POST
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method Not Allowed' });
return;
}
// Check that the request has a valid session cookie
if (!req.cookies.session) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
// Parse the JSON data from the request body
const { amount, name, iban } = req.body;
// TODO: Implement transfer logic
// Return a success message
res.status(200).json({ message: 'Transfer successful' });
}
در کد بالا دو بررسی انجام می دهیم: یکی برای روش requsetو دیگری چک کوکی سشن.
ما قصد نداریم یک وب سایت مخرب ایجاد کنیم. در عوض، ما دادههایی را که از طریق فرم یا کد جاوا اسکریپت در آن وبسایت مخرب ارسال میشوند شبیهسازی میکنیم. از آنجایی که درخواست از همان مرورگر آغاز میشود، کوکی سشن بهطور خودکار به آن متصل میشود و بک اند ما نمیتواند بین یک درخواست قانونی که توسط ما بهعنوان یک کاربر احراز هویت آغاز شده یا یک درخواست جعلی ارسال شده توسط مهاجم تمایز قائل شود.
تنها چیزی که باید از طریق این درخواست CURL ارسال کنیم، داده های فرم (مقدار، نام، iban) و کوکی سشن است. درخواست مربوطه به این صورت است:
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Cookie: session=1234" \
-d "iban=1736123125&amount=10000000&name=Criminal" \
http://localhost:3000/api/transfer
روت api/transfer
محافظت نشده پاسخ زیر را برای ما بر می گرداند:
{"name":"Criminal","iban":"1736123125","amount":"10000000"}
این پاسخ به این معنی است که ما به تازگی یک حمله CSRF را در صفحه بانکداری آنلاین با موفقیت اجرا کردیم.تا اینجا نحوه حمله CSRF را درک کردیم حال بیایم در بخش بعدی برنامه خودمان را در برابر این حمله محافظت کنیم.
چگونه از یک برنامه Next.js در برابر حملات CSRF محافظت کنیم
استفاده از نشانه های SameSite و HttpOnly
بیایید ابتدا ویژگی های SameSite
و HttpOnly
کوکی سشن خود را پیاده سازی کنیم، زیرا این کار به راحتی در یک مرحله انجام می شود. به یاد داشته باشید که ما کوکی را در روت API لاگین خود که در src/pages/api/login.js
قرار دارد، تنظیم می کنیم. بیایید تنظیمات کوکی را در روت مربوطه تنظیم کنیم:
res.setHeader("Set-Cookie", `session=${sessionId}; Path=/; Max-Age=600; SameSite=Strict; HttpOnly`);
این تنها کاری است که برای پیکربندی کوکی سشن خود باید انجام دهید. اینکه آیا شما انتخاب می کنید از یک خط مشی سختگیرانه یا سهل گیرانه استفاده کنید بستگی به این دارد که الزامات ایمنی شما چقدر بالا است و چقدر مایل به قربانی کردن از نظر تجربه کاربری هستید.
استفاده از توکن های CSRF
همانطور که در بخش های بالا گفتیم، پکیجی به نام next-csrf وجود دارد که به ما امکان می دهد به راحتی مراحل زیر را برای اطمینان از محافظت در برابر حملات CSRF اجرا کنیم:
- سرور یک توکن csrf را برای کلاینت تولید و ارسال می کند
- کلاینت/مرورگر فرمی را با توکن ارسال می کند
- سرور بررسی می کند که آیا توکن معتبر است یا نه
برای راه اندازی موفقیت آمیز یک حمله CSRF، مهاجم باید رمز CSRF را از وب سایت شما دریافت کند و برای دسترسی به آن از جاوا اسکریپت استفاده می کند. این یعنی که اگر وبسایت شما به اشتراکگذاری منابع متقاطع (CORS) اجازه ندهد، مهاجم نمیتواند به توکن CSRF دسترسی پیدا کند و به طور موثر تهدید را خنثی میکند.
برای نصب پکیج next-csrf، دستور زیر را در ریشه پروژه Next.js خود اجرا کنید:
npm i next-csrf --save
در مرحله اول، اجازه دهید next-csrf را با ایجاد یک فایل setup مقداردهی اولیه کنیم. این میدلور برای ایجاد و اعتبار سنجی توکن های CSRF ایجاد می کند:
// "lib/csrf"
import { nextCsrf } from "next-csrf";
const { csrf, setup } = nextCsrf({
// eslint-disable-next-line no-undef
secret: "12345",
});
export { csrf, setup };
در فایل env.
خود مقدار secre
t را قرار دهید.
برای راهاندازی نشانه CSRF، از یک صفحه رندر شده سمت سرور مانند صفحه لاگین استفاده میکنیم، زیرا شما از کاهش CSRF برای سختتر کردن درخواستهای خود از کاربران تأیید شده استفاده میکنید.
import Head from "next/head";
import { setup } from "lib/csrf";
export default function Home() {
return (
...
);
}
export const getServerSideProps = setup(async ({ req, res }) => {
return {
props: {},
};
});
پس از آن، تنها کاری که برای محافظت از یک روت API باید انجام دهیم این است که روت API مربوطه را با میدلور csrf
خود اعمال کنیم:
// src/pages/api/transfer.js
import { csrf } from "../../../lib/csrf";
const handler = (req, res) => {
// Check that the request method is POST
if (req.method !== 'POST') {
res.status(405).json({ error: 'Method Not Allowed' });
return;
}
// Check that the request has a valid session cookie
if (!req.cookies.session) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
// Parse the JSON data from the request body
const { name, iban, amount } = req.body;
console.log(name, iban, amount)
console.log(req.cookies.session);
// Return a success message
res.status(200).json({ name, iban, amount });
}
export default csrf(handler);
قبل از اینکه منطق درخواست واقعی انجام شود، میدلور csrf
اعتبار سنجی توکن های CSRF را انجام می دهد و در صورت عدم تایید اعتبار، یک خطا ایجاد می کند که به صورت زیر می باشد:
{"message":"Invalid CSRF token"}
نتیجه
در این مقاله، موضوع محافظت از برنامه Next.js شما در برابر حملات CSRF را پوشش دادیم و نگاهی دقیق به پکیج next-csrf داشتیم، که به شما امکان می دهد کاهش CSRF را از طریق توکن های CSRF پیاده سازی کنید. علاوه بر این، نگاهی به پیکربندی کوکیهای شما و نحوه افزایش امنیت با تنظیم مقادیر خاص کوکی داشتیم.
می توانید در بخش نظرات ، تجربیات خود از این حمله و نحوه محافظت از آن را برایمان بنویسید.