فرض کنید یک پایگاهداده دارید که شامل اطلاعات مهم هست که اصلا دوست ندارید که فرد دیگهای بهش دسترسی داشته باشه. پس اون رو پیکربندی کردین که فقط از طریق شبکه محلی (Local Network) به اون دسترسی داشته باشید.
چی میشه اگه یک وبسایت رو باز کنیم و اون وبسایت در کد جاواسکریپت خودش (که حالا روی ماشین شما و با IP شما اجرا میشه) به این سرویس (Database) درخواست بده و مقدار بازگشتی رو برای خودش برگردونه؟
Same-Origin Policy یک سازوکار امنیتی در مرورگرها هستش که ارتباط مستندات (Documents) و Scriptها رو از یک Origin با یک Origin دیگه، محدود میکنه و برای اونها قانون وضع میکنه. این سازوکار کمک میکنه که مستندات آلوده ایزوله باشن و از حملات احتمالی جلوگیری بشه. به این صورت که مرورگر اجازه نمیده که یک Origin محتوایی از Origin دیگهای بخونه و یا به اون دسترسی داشته باشه.
Origin چیست؟
دو URL رو از یک Origin یکسان میدونیم وقتی که: پروتکل، پورت (Port)، و نام دامنه (Host) اونها یکی باشه. برای روشن شدن موضوع به مثال زیر توجه کنید:
این URL رو فرض کنید http://store.company.com ، حالا قراره بررسی کنیم که کدوم یکی از موارد زیر با این URL در یک Origin یکسان هستند.
در مثال اول و دوم فقط یک مسیر (Directory) متفاوت ارائه شده و هیچ تغییری در پارامترهای مربوط به Origin مشاهده نمیکنیم. در مثال سوم از https استفاده شده که پروتکل تغییر کرده در نتیجه با URL که اول در نظر گرفتیم، در یک Origin یکسان نیستند. همچنین در مورد چهارم Port استفاده شده 81 است که با توجه به Port پیش فرض پروتکل http که 80 است، این دو URL در یک Origin یکسان نیستند. در مورد پنجم هم از یک زیردامنه (Subdomain) متفاوت استفاده شده و در نتیجه Host متفاوتی با URL اولیه داره.
قوانین SOP
شاید براتون سوال شده باشه که چرا وقتی از تگ <img> استفاده میکنید میتونید محتوای یک تصویر رو از یک URL دیگه که با URL وبسایت شما در یک Origin یکسان نیست فراخوانی کنید؟ خب باید بگم SOP برای تگ <img> قوانین متفاوتی رو داره.
برای مثال ما میتونیم یک تصویر رو از یک Origin دیگه فراخوانی کنیم و مرورگر شما بدون خطا این کار رو انجام میده:
<img src=”http://statics.example.com/cat.png” alt=”cat” height=”32″ width=”32″>
به صورت «خلاصه»:
- عملیات نوشتن (Write) بین Originهای متفاوت معمولا مجاز هست.
برای مثال یک فرم از نوع POST که میتونه از یک Origin به یک Origin دیگه فرستاده بشه.
- محتوای تعبیهشده (Embedded Content) بین Originهای مختلف معمولا مجاز هست.
برای مثال:
- کدهای جاواسکریپ: <script src=”…”></script>
- کدهای سی اس اس: <link rel=”stylesheet” href=”…”>
- فایل های چندرسانه ای: <img>, <video>, <audio>
- منابع خارجی که توسط تگ <embed> یا <object> تعبیه شده.
- فونتهای که توسط @font-face اضافه شدن (البته در بعضی از مرورگرها این مورد مجاز نیست).
- هر صفحه ای که با تگ <iframe> تعبیه شده باشه.
- البته وبسایتها میتونن با استفاده از هدر X-Frame-Options ، جلوی این تگ رو بگیرن.
- عملیات خواندن (Read) بین Originهای متفاوت معمولا غیرمجاز هست. هرچند میشه با استفاده از موارد قبلی مثل تگ <img> این کار رو انجام داد.
باز تاکید میکنم که این موارد به صورت “خلاصه” بیان شده و استفاده از کلمه معمولا به همین خاطر هست. SOP قوانین وسیعتری داره و برای خیلی از شرایط یک سیاست مجزا رو در نظر گرفته. برای مثال وبسایت آلوده نمیتونه حتی با تگ <img> از ماشین شما تصویری رو فراخوانی و SOP جلوی اینکار رو میگیره. و یا فراخوانی یک سرویس رو localhost شما توسط CORB مسدود میشه. و این نکته هم هست که SOP در هر مرورگر و نسخه اون متفاوت هست و دانش در این زمینه اغلب به تجربه بیشتر و مطالعه درباره سازوکار مرورگرها برمیگرده.
یک مثال عملی دیگه این هست که در وبسایت YouTube تلاش کنیم تا پیامهای inbox رو از سایت Reddit بگیریم. خب اولین راه حل میتونه ایجاد یک XMLHttpRequests باشه:
همونطور که میبینید بدون هیچ مشکلی تونستیم روی URL مورد نظر GET کنیم. اما یک پاسخ خالی دریافت کردیم. چرا؟ به صورت پیشفرض یک جلسه (Session) XMLHttpRequests درخواست هارو با اعتبارنامه های (Credential) ذخیره شده نمیفرسته و این از نظر SOP مجاز هست. خب الان میخوام که اعتبارنامه رو همراه درخواستم بفرستم (من در مرورگرم تو Reddit لاگین هستم):
همونطور که میبینیم درخواست من توسط CORS مسدود شد.
در مرحله بعدی با تگ <img> شانسمون رو امتحان میکنیم:
خب درخواستمون با موفقیت ارسال شد و اعتبارنامه ها همراه درخواست ما بودن. اما در پاسخ (Response) چیزی نمیبینیم. چرا؟ خب اینبار CORB به علت اینکه MIME Type اطلاعات بازگشتی از نوع application/json بوده محتوا بازگشتی رو مسدود کرده.
یک نکته مهم این هست که Cookieها در SOP ، فقط نام دامنه رو چک میکنن و پروتکل و Port رو نادیده میگیرن. برای مثال هیچ Cookie نمیتونه بین دو Host متفاوت به اشتراک گذاشته بشه (حتی Subdomain). البته در مورد زیردامنه ها میشه مقدار domain رو در هدر Set-Cookie در نظر گرفت تا این اجازه داده بشه و زیردامنه ها بتونن از همون Cookie استفاده کنند. برای جزئیات بیشتر در این زمینه به RFC 6265 مراجعه کنید.
خب متوجه شدیم که چرا یک وبسایت نمیتونه محتوای دیگهای رو از روی ماشین شما یا وب سایت دیگهای با Origin متفاوت فراخوانی کنه و به اطلاعات شما دسترسی داشته باشه. اما هنوز یک کد جاواسکریپ آلوده میتونه یک فرم رو POST کنه و محتوای بازگشتی رو دریافت کنه بدون اینکه SOP اون رو مسدود کنه. خب در این موارد چی؟
حملات CSRF
خب تا الان خیلی از چیزهایی که باهم خوندیم موضوع این حمله بودند. حمله CSRF نوعی از حمله در هست که در اون متخاصم (Attacker) سعی میکنه کاربر رو مجبور به اجرای درخواستهای ناخواسته بکنه. که این درخواستها معمولا روی وبسایتهایی هست که کاربر روی اونها لاگین کرده. برای مثال سناریو زیر رو فرض کنید:
- متخاصم یک پیوند (Link) به یک وبسایت آلوده رو به هرصورتی برای کاربر میفرسته.
- کاربر با این وبسرویس ارتباط بر قرار میکنه و با صفحه معمولی از یک وبسایت مواجه میشه.
- هرچند در این صفحه یک فرم وجود داره که با استفاده از یک کد جاواسکریپت بصورت خودکار اجرا میشه.
- و فرض بر اینکه کاربر در وبسایت slack.com لاگین باشه، یک درخواست vote میفرسته.
و به همین صورت یه سرویس رای تقلبی ساخته میشه. و همونطوری که در قبل خوندیم یک فرم POST از نظر SOP مجاز هست.
استفاده از علامترمزی (Token) برای ارتباط با وب سایت
همونطور که دیدیم با استفاده از حملات CSRF میشد بدون اطلاع کاربر درخواست POST فرستاده بشه. خب راهحل برای جلوگیری از این اتفاق چیه؟ طبیعتا نمیتونه جلوگیری از POST یک فرم باشه، برای مثال فرض کنید قرار هست از طریق google به یک وبسایتی لاگین بشیم و این درخواست بین Originها لازمه.
در اینجا ما از یک علامترمزی استفاده میکنیم که به اون CSRF Token گفته میشه و کاملا بیمعنی هستش. به این صورت که هنگامی که کاربر وارد وبسایت میشه یک Cookie روی مرورگر ثبت میشه که شامل اطلاعات اون نشست (Session) هست. و همینطور در محتوای HTML صفحه یک علامترمزی ثبت شده. حالا کاربر برای هر درخواست POST، این علامت رو به همراه Cookie که ذخیره شده ارسال میکنه و وبسرویس ما این دو مقدار رو باهم چک میکنه تا با سیاستهای مورد نظرش منطبق باشه، برای مثال سرور تضمین میکنه که CSRF Token با شناسه نشست (Session ID) که در قالب Cookie فرستاده شده مرتبط باشه و همچنین نشست هنوز منقضی (Expired) نشده باشه.
این سازوکار باعث میشه که فرد سوم که قصد داره بدون اطلاع شما این درخواست POST رو انجام بده نیاز داشته باشه به اون علامترمزی که تنها با درخواست GET در دسترس هست و عملا بدون اون امکان ارسال یک درخواست معتبر نیست.
به عنوان یک مثال عملی نگاه میکنیم به اطلاعات صفحه ورود وب سایت Reddit:
همونطور که میبینیم، در Cookie شناسه نشست ثبت شده. همچنین در پایین میبینیم که یک csrf_token در صفحه HTML برای ما ثبت شده.
حالا یک درخواست Login میفرستیم، و میبینیم در قالب Data درخواست مقدار csrf_token ارسال شده. و همچنین پاسخ هم میگه نام کاربری یا رمزعبورمون اشتباه بوده.
و حالا سعی میکنیم همین درخواست رو با عوض کردن مقدار csrf_token دوباره ارسال کنیم. اینبار پاسخ متفاوت هست و نشون میده که توکن ارائه شده صحیح نیست و نیاز هست که صفحه رو دوباره GET کنیم. به این صورت از حمله CSRF جلوگیری شد.
هرچند برای عملیات های مهم تر مثل حذف اکانت یا جابه جایی یه مبلغ، بهتر هست که دوباره از کاربر پسورد خواسته بشه و یا حتی از Captcha استفاده بشه. Captcha هم یکی دیگه از روش های جلوگیری از حملات CSRF هست.
پارامتر SameSite در Cookie
هرچند امروزه بیشتر تکنولوژی های وب خودشون پیش فرض سازوکار جلوگیری از این حمله رو پیاده میکنن، ولی بد نیست با یکی دیگه از روش های جلو گیری از این حمله آشنا بشیم.
پارامتر SameSite یک ویژگی از Cookieهاست که در RFC6265 تعریف شده. این ویژگی به مرورگر میگه که آیا اجازه داره از Cookie ذخیره شده موقع درخواست های بین سایتی (Cross-Site) استفاده کنه یا نه. این ویژگی سه مقدار مختلف داره:
- Strict: این مقدار تضمین میده که مرورگر از Cookie شما برای درخواست های بینسایتی استفاده نمیکنه، هرچند این باعث افت راحتی کابر میشه ولی برای مواردی با سطح امنیتی بالا لازمه.
- Lax: این مقدار با سیاستهایی که براش تعریف شده عمل میکنه و در مواقع مورد نیاز مرورگر Cookie رو استفاده میکنه، این مقدار انتخاب بهتری هست و همزمان ایمنی و راحتی کاربر رو درنظر میگیره.
- None: این مقدار هم که در تمام موارد از Cookie استفاده میکنه.
هرچند این ویژگی قادر به جلوگیری از حملات CRSF هست اما استفاده اون به جای Token های اصلا درست نیست. امروزه اغلب مرورگرها از این ویژگی پشتیبانی میکنن ولی در مواقعی ممکن هست که مرورگر از این ویژگی پشتیبانی نکنه و این سازوکار درست عمل نکنه.
برای اطلاعات بیشتر در زمینه جلوگیری از حملات CSRF میتونید به این لینک مراجعه کنید:
مطالبی که می نویسم صرفا خلاصه مطالعه RFC ها و کتابهای معروفه که همتون میشناسید. و هیچ کدوم از این ها کشفیات خودم نبوده و نیست. خوشحال میشم نقد کنید و اگه موردی از نظرتون درست نبود بهم بگید.
راه ارتباطی با من معمولا aggr3ssor@protonmail.com هست و در توییتر با 0xc0d میتونید منو پیدا کنید.
[…] پست قبلی درباره حملات CSRF خوندیم، اینکه چطوری یک وبسایت آلوده میتونه کاربر […]
ممنون ، عالی بود
بسیار مقالات خوب و ارزشمندی در اختیار عموم قرار میدین واقعا عالی هستین موفق باشین.