عنوان:

‫امن سازی درخواست‌های ای‌جکسی برنامه‌های ASP.NET MVC 5.x در مقابل حملات CSRF


نویسنده: اختیاری
تاریخ: ۱۳۹۶/۰۷/۲۰ ۱۹:۳۰
آدرس: www.dntips.ir

طی مقاله چک لیست تولید برنامه Asp.net mvc و بررسی امنیتی ای‌جکس هنگام استفاده در مورد چک لیست امنیتی سایت سرفصل‌های مهم عنوان و بررسی شده است که یکی از موارد، مقاوم ساختن وب اپلیکشن در برابر حملات CSRF می‌باشد. اینگونه حملات بر پایه این استراتژی شکل می‌گیرند که با ارسال درخواستی به نیابت از سمت سیستم/مرورگر کاربر تایید هویت شده، سایت مقصد را مجبور به انجام عملی کند. برای مثال اگر شما در سایت a.com یک کاربر تایید شده باشید و هم اکنون در سایت فوق نیز لاگین باشید، مهاجم با ارسال یک برنامه/صفحه یا موارد مشابه و در قالب src یک عکس یا با ترغیب شما با کلیک بر روی یک لینک با href آلوده یا موارد مشابه، از سمت مرورگر شما درخواستی را به سمت سایت a.com ارسال می‌کند .

این درخواست ممکن است شامل حذف اطلاعات، تغییر مشخصات، پرداخت هزینه یا موارد مشابه باشد. جهت مقابله با این حمله، یکی از موارد مهم، استفاده همیشگی از Html.AntiForgeryToken() در تمامی فرم‌های ورود اطلاعات است. همچنین استفاده همیشگی از متد Post و بررسی تایید مبدا درخواست‌های ای‌جکسی، بررسی http referrer ، محدود کردن طول عمر کوکی، استفاده از کپچهای قوی مانند کپچای گوگل می‌تواند تا حد زیادی وب اپلیکیشن را در مورد اینگونه حملات، مصون کند.

در این بین یکی از موارد دیگر، اضافه کردن AntiForgeryToken به درخواست‌های ا‌ی‌جکسی سایت می‌باشد. جهت حصول این منظور، راه‌های مختلفی موجود است. یکی از راه حل‌ها استفاده از یک هلپر جهت تولید توکن مورد نظر است.

ساختار هلپر مورد نظر به شرح زیر است :

public static class AntiForgeryToken
{
  public static MvcHtmlString AntiForgeryTokenForAjaxPost(this HtmlHelper helper)
  {
    var antiForgeryInputTag = helper.AntiForgeryToken().ToString();
    // Above gets the following: <input name="__RequestVerificationToken" type="hidden" value="some value" />
    var removedStart = antiForgeryInputTag.Replace(@"<input name=""__RequestVerificationToken"" type=""hidden"" value=""", "");
    var tokenValue = removedStart.Replace(@""" />", "");
    if (antiForgeryInputTag == removedStart || removedStart == tokenValue)
       throw new InvalidOperationException("Oops! The Html.AntiForgeryToken() method seems to return something I did not expect.");
    return new MvcHtmlString($@"{"__RequestVerificationToken"}:""{tokenValue}""");
  }
}
کار آن حذف فیلد مخفی این توکن و درج آن به صورت یک شیء جاوا اسکریپتی است. 

در مرحله بعد طبق الگوی زیر، درخواست ا‌ی‌جکسی به همراه توکن تولید شده و به کنترلر ارسال خواهد شد:
function AddToCart(pid) {
   $.ajax({
     url: '@Url.Action("AddToBasket","Shop")',
     data: { 'pid': pid,@Html.AntiForgeryTokenForAjaxPost()  },
     type: 'post',
     success:function(e) {
       //do something
     }
   });
}

در مرحله آخر، باید کنترلر مورد نظر شامل ویژگی‌های [HttpPost] [ValidateAntiForgeryToken]  باشد تا صحت توکن تولیدی را بررسی کند و در صورت نامعتبر بودن، از اجرای دستورات جلوگیری گردد.

نظرات

  • اختیاری در ۱۳۹۶/۰۷/۲۸ ۹:۵
    نکته : اگر از چنین هلپری در NetCore استفاده می‌کنید با توجه به موجود نبود mvchtmlstring  در نسخه جدید می‌تواند از HtmlString بهره ببرید.
    • محسن خان در ۱۳۹۶/۰۷/۲۸ ۹:۱۸
      این روش عمومی‌تر است:
      function addToken(data) {
         data.__RequestVerificationToken = $("input[name=__RequestVerificationToken]").val();
         return data;
      }
      برای
      $.ajax({
         // .....
         data: addToken({ postId: postId }), // اضافه کردن توکن
         dataType: "html", // نوع داده مهم است
         // .....
      });
      که معادل قطعه کد زیر هست:
       data: { "pid": pid, "__RequestVerificationToken": $('[name=__RequestVerificationToken]').val()  },
      البته به نظر در این حالت "dataType: "html باشد تا کار کند.
      حالت سراسری آن هم تحت نظر قرار دادن رخ‌داد مخصوص ارسال‌های Ajax ای هست (نوشتن یک interceptor):
      $(document).ready(function () {
                  var securityToken = $('[name=__RequestVerificationToken]').val();
                  $('body').bind('ajaxSend', function (elm, xhr, s) {
                      if (s.type == 'POST' && typeof securityToken != 'undefined') {
                          if (s.data.length > 0) {
                              s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
                          }
                          else {
                              s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
                          }
                      }
                  });
              });
      • طیب در ۱۳۹۶/۰۷/۲۸ ۱۰:۲۵
        توی این قسمت 
        function addToken(data) {
           data.__RequestVerificationToken = $("input[name=__RequestVerificationToken]").val();
           return data;
        }
        کجا 
          data: addToken ( { postId: postId } ) , // اضافه کردن توکن
          رو اضافه میکنه ؟ (postId)
        • محسن خان در ۱۳۹۶/۰۷/۲۸ ۱۰:۳۳
          برای درک عملکرد قطعه کد نوشته شده و ماهیت پویای اشیای جاوا اسکریپتی، این مطلب را مطالعه کنید: «برنامه نویسی پیشرفته JavaScript - قسمت 3 - انواع ارجاعی و نحوه‌ی ایجاد اشیاء» 
          • طیب در ۱۳۹۶/۰۷/۲۸ ۱۰:۵۲
            ممنونم. مطلب رو خوندم. ولی باز برام سواله، در کد شما postId در کجای تابع addToken  مقدارش ست میشه. آیا نباید به این شکل باشه:
            function addToken(data) {
               data.__RequestVerificationToken = $("input[name=__RequestVerificationToken]").val();
               data.postId=postId;
               return data;
            }
            • محسن خان در ۱۳۹۶/۰۷/۲۸ ۱۰:۵۷
              مقدارش قبلا تنظیم شده‌است. data ایی که به اون ارسال شده حاوی postId هست ({} یعنی یک شیء حاوی اطلاعات و خواص جاوا اسکریپتی). در اینجا فقط به صورت پویا یک خاصیت جدید به آن اضافه می‌شود. جاوا اسکریپت یک زبان dynamic هست. مثل واژه‌ی کلیدی dynamic در #C که بعدش می‌تونید خواصی را به دلخواه به این نوع اشیاء اضافه کنید. همان توضیحات مطلبی که لینک دادم.
  • اختیاری در ۱۳۹۶/۰۸/۰۲ ۱۱:۲۶
    مباحث تکمیلی  در مورد AntiForgery و نحوه پیاده سازی به صورت عمومی و همیشگی در Asp.net Core