من اخیراً در حین نوشتن مقالهای در مورد فهم Asynchronous JavaScript ، به تحقیق درباره امنیت وب و حملات سایبری، از جمله CSRF پرداختم. چرا که میخواستم مطمئن شوم توصیههایم ایمن هستند و با آنها به هیچ هنرجویی آسیب نمیرسانم.
متأسفانه، درک مقالات در فضای امنیتی بسیار سخت بود. کلمات زیادی وجود داشت که باعث ترس، شک و عدم اطمینان میشد. وقتی این مقالات را میخوانم، دچار حس وحشت میشوم. و نگرانم که ممکن است کار اشتباهی انجام دهم، حتی اگر این مقالات نیتشان خیر باشد!
همچنین بسیاری از مقالات جزئیات کاملی در مورد CSRF ، نحوه راهاندازی یک حمله CSRF و نحوه جلوگیری از حملات CSRF را توضیح نمیدهند. که این موضوع باعث میشود در مورد آنچه که یاد گرفتهام شک کنم. در نهایت مجبورم همه چیز را خودم یاد بگیرم.
من میخواهم درک CSRF را برای شما آسانتر کنم. بنابراین تلاش کردم مقالهای با اطلاعات کامل (و گام به گام) درباره حملات CSRF بنویسم. امیدوارم این مقاله روشنی لازم و اعتماد به نفس کافی برای ایجاد اپلیکیشنهای امنیت وب را به شما بدهد.
دو نوع حمله CSRF
دو نوع حمله CSRF وجود دارد:
- حمله معمولی CSRF
- ورود به سیستم CSRF
ابتدا در مورد حمله معمولی CSRF و سپس ورود به سیستم CSRF صحبت خواهیم کرد.
حمله CSRF چیست؟
حمله CSRF حملهای است که قربانی را فریب میدهد تا به وب سایتی که در آن احراز هویت (برای ورود به سیستم) انجام میشود، درخواستی مخرب ارسال کند. درخواستی که او قصد ارسالش را نداشته است.
درخواست باید از سمت وب سایت دیگری باشد که به این نوع درخواستها Cross-Site میگویند. این درخواست همچنین هویت یک کاربر تأیید شده را جعل میکند که به این کار Request Forgery میگویند.
حملات CSRF، کور (blind ) هستند. به این معنی که مهاجم نمیبیند پس از ارسال درخواست توسط قربانی چه اتفاقی میافتد. حملات CSRF اغلب هدفشان تغییر وضعیت در سرور است.
تغییر وضعیت (state change ) چیست؟ اساساً هر چیزی که دیتابیس را modify کند یک تغییر وضعیت است.
نمونههایی از تغییرات وضعیت عبارتاند از:
- تغییر یوزرنیم و پسوورد
- ارسال پول به یک حساب
- ارسال پیام جعلی از سمت کاربر
- به اشتراک گذاشتن تصاویر و ویدئوهای نامناسب از سمت اکانت کاربر
حملات CSRF از این واقعیت که مرورگرها در هر درخواست به طور خودکار کوکیها را به سرور ارسال میکنند، استفاده میکنند. بدون هیچ گونه محافظت CSRF ، سرور ممکن است فرض کند که یک درخواست در زمان وجود کوکی احراز هویت، معتبر است.
کوکیهای احراز هویت ممکن است تا زمانی که سرور از آنها برای بررسی معتبر بودن یک کاربر استفاده میکند، هر چیزی باشند. میتوانند توکن دسترسی، یا یک session ID باشند. بستگی به این دارد که سرور چگونه احراز هویت را مدیریت میکند.
پیشنیاز عملیات CSRF Attack
چهار پیشنیاز برای موفقیت در یک حمله CSRF وجود دارد.
- درخواست به هر روشی به سرور ارسال شود.
- کاربر باید احراز هویت شود.
- سرور باید اطلاعات احراز هویت را در کوکیها ذخیره کند.
- سرور تکنیکهای پیشگیری از CSRF را اجرا نکند. (که در خطوط زیر، مورد بحث قرار خواهد گرفت.)
نحوه عملکرد حملات CSRF
قبل از اینکه مهاجم بتواند یک حمله CSRF را راهاندازی کند، باید یک درخواست ثابت پیدا کند که بتواند آن را هدف قرار دهد. آنها میدانند که این درخواست چه کار قرار میکند. این میتواند هر درخواستی باشد: GET، POST ، PUT یا DELETE.
هنگامی که آنها درخواست هدف را انتخاب کردند، باید یک درخواست جعلی برای فریب کاربر ایجاد کنند.
در نهایت، آنها باید کاربر را برای ارسال درخواست فریب دهند. بیشتر اوقات این اتفاق به این معنی است:
راهی برای ارسال خودکار درخواست، بدون اطلاع کاربر پیدا کنید. رایجترین رویکرد در این کار از طریق تگهای image و ارسال خودکار فرم جاوا اسکریپت است.
ارائه نادرست لینک (یا دکمه) که کاربر را برای کلیک کردن روی آن فریب بدهد. ( مهندسی اجتماعی AKA )
حملات از طریق درخواست GET
حملات CSRF با درخواست GET تنها در صورتی کار میکنند که سرور به کاربر اجازه دهد تا وضعیت را با درخواستهای GET تغییر دهد. اگر درخواستهای GET شما read-only هستند، لازم نیست نگران این نوع حمله CSRF باشید.
اما فرض کنید سروری داریم که از بهترین شیوههای برنامه نویسی پیروی نمیکند و از طریق درخواست GET اجازه تغییر حالت میدهد. اگر آنها اینکار را انجام دهند، دچار مشکل و دردسر بزرگی خواهند شد.
به عنوان مثال، فرض کنید بانکی وجود دارد که با شما این امکان را میدهد که با endpoint زیر پول منتقل کنید. تنها باید account و amount را در درخواست GET برای ارسال پول به یک شخص به صورت زیر وارد کنید:
https://bank.com/transfer?account=Mary&amount=100
مهاجم میتواند پیوندی ایجاد کند که پول را به حساب خود ارسال کند.
# Sends 9999 to the Attacker's account
https://bank.com/transfer?account=Attacker&amount=9999
در این مرحله، مهاجم میتواند راهی برای راهاندازی لینک به طور خودکار، و بدون اطلاع کاربر پیدا کند.
ترفندهای خطرناک
یک راه این است که پیوند را در یک تصویر 0x0 در یک صفحه وب یا ایمیل قرار دهید. اگر کاربر از این صفحه وب یا ایمیل بازدید کند، درخواست GET به طور خودکار فعال میشود. زیرا مرورگرها و ایمیلها برای fetch خودکار تصاویر پیکربندی شدهاند.
(الان متوجه شدم که چرا ارائهدهندگان ایمیل، بارگذاری تصاویر را به عنوان یک اقدام احتیاطی غیرفعال میکنند.)
راه دیگر این است که آنچه را یک پیوند انجام میدهد، به اشتباه معرفی کنید. این کار به این دلیل جواب میدهد که مردم لینکها را قبل از کلیک کردن روی آنها بررسی نمیکنند. اگر شخص روی یک لینک کلیک کند، بدون اینکه بداند درخواست GET را برای مهاجم ارسال کرده است.
اگر کاربر احراز هویت شود، سرور یک کوکی احراز هویت دریافت میکند که باعث میشود باور کند درخواست معتبر است. اگر سرور از هیچ مکانیزم حفاظتی CSRF استفاده نکرده باشد، پول برای مهاجم ارسال میشود.
نمونههایی از حملات GET CSRF :
- uTorrent در سال 2008 با یک حمله CSRF مواجه شد و اجازه تغییر وضعیت با درخواستهای GET را داد.
- YouTube پیشتر در سال 2008 یک آسیبپذیری امنیتی داشت که به مهاجم اجازه میداد تقریباً تمام امکانات ممکن برای یک کاربر را انجام دهد. از جمله ارسال پیام، افزودن به لیست دوستان و غیره.
اگر روی لینکهای بالا کلیک کنید، میتوانید نمونههایی از درخواستهای GET واقعی را پیدا کنید که حملات CSRF اینچنینی ایجاد میکنند. (نگران نباشید، هیچ لینک خطرناکی اینجا نیست 😜 )
حملات CSRF با درخواستهای POST
حملات CSRF با درخواستهای POST از الگوی مشابهی پیروی میکنند، اما نمیتوانند از طریق لینکها یا تگهای image ارسال شوند. آنها باید از طریق یک فرم یا از طریق جاوا اسکریپت ارسال شوند.
بیایید فرض کنیم که endpoint آسیبپذیر یکسانی داریم و مهاجم برای راهاندازی درخواست، فقط باید اطلاعات account و amount را وارد کند.
POST https://bank.com/transfer?account=Attacker&amount=9999
مهاجم میتواند یک فرم ایجاد کند و مقادیر account و amount را از کاربر پنهان کند. افرادی که بر روی این فرم نادرست کلیک میکنند، بدون اینکه بدانند درخواست POST را ارسال میکنند.
این فرم همچنین میتواند به طور خودکار و بدون اطلاع افراد، با جاوا اسکریپت اجرا شود. کاربران واقعی حتی بدون کلیک کردن بر روی یک دکمه، دچار مشکل میشوند.
حملات POST CSRF ترسناک هستند، اما راههایی برای جلوگیری از آنها وجود دارد. در بخش پیشگیری، در مورد تکنیکهای حفاظتی صحبت خواهیم کرد.
حملات CSRF با درخواستهای PUT و DELETE
حملات CSRF را نمیتوان با درخواستهای PUT یا DELETE اجرا کرد، زیرا تکنولوژی مورد استفاده ما، به آنها اجازه نمیدهد.
بله. درست خواندید!
حملات CSRF را نمیتوان از طریق فرمهای HTML اجرا کرد زیرا فرمها از درخواستهای PUT و DELETE پشتیبانی نمیکنند. فقط GET و POST جواب میدهند. اگر از هر روش دیگری استفاده کنید (به جز GET و POST )، مرورگرها به طور خودکار آنها را به درخواست GET تبدیل میکنند.
شما هرگز نمیتوانید یک حمله CSRF را از طریق HTML اجرا کنید.
حالا یک چیز بامزه! چگونه افراد درخواست PUT و DELETE را از طریق یک فرم ارسال میکنند، اگر HTML اجازه آن را نمیدهد؟ پس از مدتی تحقیق، متوجه شدم که اکثر فریمورکها به شما این امکان را میدهند که با استفاده از پارمتر _method ، یک درخواست POST ارسال کنید.
شما میتوانید یک حمله PUT CSRF را از طریق جاوا اسکریپت اجرا کنید، اما مکانیزم پیشفرض پیشگیری در مرورگرها و سرورهای امروزی، انجام این حملات را واقعاً سخت میکند. شما باید عمداً سپر دفاعی را کنار بزنید تا این اتفاق بیفتد. اما دلیل آن چیست؟
اجرای حمله PUT CSRF
برای اجرای یک حمله PUT CSRF ، باید یک Fetch request با متد put ارسال کنید. همچنین باید گزینه credentials را نیز وارد کنید.
const form = document.querySelector('form')
// Sends the request automatically
form.submit()
// Intercepts the form submission and use Fetch to send an AJAX request instead.
form.addEventListener('submit', event => {
event.preventDefault()
fetch(/*...*/, {
method: 'put'
credentiials: 'include' // Includes cookies in the request
})
.then(/*...*/)
.catch(/*...*/)
})
به سه دلیل، این کار نمیکند.
اول اینکه درخواست به دلیل CORS اتوماتیکوار توسط مرورگرها اجرا نمیشود. مگر اینکه – البته – سرور با اجازه دادن به درخواستهای هرکسی با header زیر آسیبپذیری ایجاد کند:
Access-Control-Allow-Origin: *
دوم اینکه حتی اگر به همه منابع اجازه دسترسی به سرور خود را بدهید، همچنان به منظور ارسال کوکیها به سرور در مرورگرها، به یک گزینه Access-Control-Allow-Credentials احتیاج دارید.
Access-Control-Allow-Credentials
سوم اینکه حتی اگر اجازه دهید کوکیها به سرور ارسال شوند، مرورگرها فقط کوکیهایی را ارسال میکنند که ویژگی sameSite شان روی none تنظیم شده باشد. (به اینها کوکیهای شخص ثالث نیز گفته میشود.)
اگر در سومین نکته، نمیدانید که درباره چه چیزی صحبت میکنم، در امان هستید. شما واقعاً باید یک توسعهدهنده مخرب باشید، اگر بخواهید با ارسال کوکیهای احراز هویت به عنوان کوکیهای شخص ثالث، یک سرور را خراب کنید.
به طور خلاصه، شما فقط باید نگران حملات POST CSRF باشید، مگر اینکه واقعاً سرور خود را خراب کرده باشید.
روشهای پیشگیری از CSRF
رایجترین متدهای پیشگیری از CSRF در حال حاضر عبارتاند از:
- Double Submit Cookie pattern
- Cookies header method
هر دو روش از یک فرمول پیروی میکنند.
هنگامی که کاربر از وب سایت شما بازدید میکند، سرور شما باید یک توکن CSRF ایجاد کند و آنها را در کوکیهای مرورگر قرار دهد. نامهای رایج برای این توکن عبارتاند از:
- CSRF-TOKEN
- X-SRF-TOKEN
- X-XSRF-TOKEN
- X-CSRF-TOKEN
از هر نام توکنی که ترجیح میدهید استفاده کنید. همه آنها کار میکنند.
آنچه مهم است این است که توکن CSRF باید یک رشته رمزنگاری قوی و تولید شده به صورت رندوم باشد. اگر از Node استفاده میکنید، میتوانید رشته را با crypto تولید کنید.
import crypto from 'crypto'
function csrfToken (req, res, next) {
return crypto.randomBytes(32).toString('base64')
}
اگر از Express استفاده میکنید، میتوانید این توکن CSRF را به صورتی که میبینید در کوکیهای خود قرار دهید. در حین انجام این کار، توصیه میکنم از یک strict option به نام sameSite استفاده کنید. (در مورد sameSite کمی بعد صحبت خواهیم کرد.)
import cookieParser from 'cookie-parser'
// Use this to read cookies
app.use(cookieParser())
// Setting CSRF Token for all endpoints
app.use(*, (req, res) => {
const { CSRF_TOKEN } = req.cookies
// Sets the token if the user visits this page for the first time in this session
if (!CSRF_TOKEN) {
res.cookie('CSRF_TOKEN', csrfToken(), { sameSite: 'strict' })
}
})
نحوه استفاده از توکن CSRF ، بسته به اینکه از الگوی ارسال کوکی دوگانه پشتیبانی میکنید یا روش Cookie to header ، یا هر دو، تغییر میکند.
الگوی Double Submit Cookie
نام این الگو کمی گمراهکننده است، زیرا به نظر میرسد به معنای دوبار ارسال کردن یک کوکی با “Double Submit Cookie” است.
معنی آن این است که شما میتوانید توکن CSRF را در یک کوکی ارسال کنید.
شما <form> را با یک توکن CSRF رندر میکنید، که در submission فرم گنجانده میشود. (از این رو submission دوبل میشود.)
اگر از Express استفاده میکنید، میتوانید توکن CSRF را به شکل زیر به HTML منتقل کنید:
app.get('/some-url', (req, res) => {
const { CSRF_TOKEN } = req.cookies
// Render with Nunjucks.
// Replace Nunjucks with any other Template Engine you use
res.render('page.nunjucks', {
CSRF_TOKEN: CSRF_TOKEN
})
})
سپس میتوانید از فرم CSRF_TOKEN به این شکل استفاده کنید:
سپس سرور میتواند اعتبار session را با مقایسه دو توکن CSRF بررسی کند. اگر مطابقت داشته باشند، به این معناست که درخواست جعلی نیست. زیرا هیچ راهی برای مهاجم وجود ندارد که بتواند مقدار توکن CSRF را در وبسایت دیگری حدس بزند.
// Checks the validity of the CSRF Token
app.post('/login', (req, res) => {
const { CSRF_TOKEN } = req.cookies
const { csrf } = req.body
// Abort the request
// You can also throw an error if you wish to
if (CSRF_TOKEN !== csrf) return
// ...
})
متد Cookie to Header
متد Cookie to Header نیز همین شکلی است. با این تفاوت که با جاوا اسکریپت اجرا میشود. در این مورد، توکن CSRF باید هم در کوکی و هم در هدر درخواست گنجانده شود.
در این مورد ما نیاز داریم که:
- Credentials را به include یا same-origin تنظیم کنیم که کوکیها را شامل شود.
- توکن CSRF را از cookies بگیریم و آن را به عنوان هدر درخواست اضافه کنیم.
در اینجا یک نمونه درخواست وجود دارد:
// Gets the value of a named cookie
function getCookie () {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
if (match) return match[2]
}
// Sends the request
fetch('/login', (req, res) => {
credentials: 'include',
headers: {
'CSRF_TOKEN': getCookie('CSRF_TOKEN')
}
})
سرور میتواند اعتبار CSRF را به شکل زیر بررسی کند:
// Checks the validity of the CSRF Token
app.post('/login', (req, res) => {
const { CSRF_TOKEN } = req.cookies
const { CSRF_TOKEN: csrf } = req.headers
// Abort the request
// You can also throw an error if you wish to
if (CSRF_TOKEN !== csrf) return
// ...
})
همه این کارها را با یک کتابخانه آسانتر کنید
من به شما نشان دادم که چگونه توکنهای CSRF را به صورت دستی ایجاد و تست کنید، زیرا میخواستم درک درستی از این روند به شما ارائه دهم.
این فرآیند قبلاً بارها حل شده است. بنابراین ما نباید آن را به صورت دستی انجام دهیم. (مگر اینکه در حال یادگیری باشید. مانند کاری که من اینجا انجام دادم.)
اگر از Express استفاده میکنید، توصیه میکنم از کتابخانه csurf استفاده کنید. زیرا در مقایسه با آنچه که در مثال بالا دیدید، قویتر و انعطافپذیرتر است.
SameSite Cookie attribute
تنظیم sameSite به strict در مثال بالا، تضمین میکند که کوکی توکن CSRF تنها در صورتی به سرور ارسال میشود که درخواست از همان وب سایت گرفته شود. این تضمین میکند که توکن CSRF هرگز به صفحات خارجی نشت نکند.
شما میتوانید –اختیاری است اما توصیه میشود – همانطور که کوکی احراز هویت را تنظیم میکنید، sameSite attribute را بر روی strict قرار دهید. این تضمین میکند که هیچ نوع حمله CSRF نتواند انجام شود. زیرا کوکیهای احراز هویت دیگر در درخواستهای بین سایتی گنجانده نمیشوند.
اگر از تنظیم sameSite بر strict برای کوکی احراز هویت خود استفاده میکنید، آیا به حفاظت توکن CSRF نیاز دارید؟
من میگویم در بیشتر موارد خیر. زیرا sameSite الان هم از سرور در برابر درخواستهای بین سایتی محافظت میکند. ما هنوز برای محافظت در برابر یک نوع خاص از CSRF ، به توکن CSRF نیاز داریم: Login CSRF (ورود به سیستم CSRF )
Login CSRF
هدف Login CSRF از هر نظر با حملات عادی CSRF متفاوت است.
در Login CSRF ، مهاجم کاربر را فریب میدهد تا با credentials مهاجم وارد سیستم شود. پس از موفقیتآمیز بودن حمله، کاربر در صورت عدم توجه، به استفاده از حساب مهاجم ادامه خواهد داد.
آنها همچنین میتوانند فرم را به طور خودکار با جاوا اسکریپت فعال کنند.
const form = document.querySelector('form')
// Sends the request automatically
form.submit()
اگر کاربر متوجه نشود که به حساب مهاجم وارد شده است، ممکن است اطلاعات شخصی، مانند اطلاعات کارت اعتباری یا search history را به حساب اضافه کند. در نتیجه مهاجمان میتوانند برای مشاهده این دادهها، دوباره وارد حساب خود شوند.
گوگل در گذشته در برابر حملات Login CSRF آسیبپذیر بوده است.
ما میتوانیم با الگوی Double Submit Cookie که در بالا ذکر شد، از Login CSRF جلوگیری کنیم. بدین ترتیب مهاجمان نمیتوانند توکن CSRF را حدس بزنند، به این معنی که نمیتوانند یک حمله CSRF Login را راهاندازی کنند.
نتیجهگیری
CSRF مخفف عبارت across Site Request Forgery است. دو نوع حمله CSRF وجود دارد:
- Normal CSRF
- Login CSRF
در CSRF معمولی یا Normal CSRF ، هدف مهاجم ایجاد تغییر وضعیت از طریق یک درخواست است.
در Login CSRF ، مهاجم قصد دارد کاربر را فریب دهد تا وارد حساب مهاجم شود. و امیدوار است که در صورت ناآگاهی کاربر، از اقدامات او سوء استفاده کند.
با الگوی Double Submit Cookie و متد Cookie to header میتوانید از هر دو نوعِ حملات CSRF جلوگیری کنید. تنظیم sameSite روی strict از CSRF معمولی جلوگیری میکند، اما از Login CSRF خیر.
همین بود!
منبع: HackerNoon نویسنده: Zell Liew
Leave feedback about this