جلوگیری از حملات دستکاری آدرس در ASP.NET MVC‌

یکی از ویژگی های ASP.NET MVC نحوه آدرس دهی صفحات و منابع یا همون URL هست که تر و تمیزه و از سوی کاربر قابل فهمه یا به عبارتی SEO Friendly URLs ه.مثلا اگه شما Controller ی بنام Profile داشته باشید که یکی از Action Method های اون Index باشه موقع کار با این Controller آدرس های ارسالی به صورت زیر میشن.

www.dotnetdev.info/Profile/2

همه چی آرومه! تا اینکه یه کاربر کنجکاو اون 2 ی آخر URl رو میکنه 3 و…
اینجاس که وب سایت شما در مقابل «حمله دستکاری آدرس» یا URL Manipulation Attack یا Parameter Manipulation ضعف داره.
و اماچه باید کرد؟
برای جلوگیری از این حمله روش های مختلفی موجود که بعضیاش شکننده و آماتور و بعضیاش حرفه ای ن و البته راه حل نهایی.
همونطور که گفتم روش های متفاوتی موجوده که بر میگرده به سناریوی ما که مهمترینش وجود رابطه بین درخواست ارسالی از سمت کاربر و هویت اون کاربره مثلا تو مثال بالا حتما رابطه یک به یک بین پروفایل و کاربر موجوده و یا مثلا یک سیستم اتوماسیون اداری رو در نظر بگیرید بین درخواست های ایجاد شده در سیستم و کاربر رابطه موجوده.خب حالا این رابطه ها کجا بکار میاد ؟
زمانی که شما به درخواستی جواب میدید براحتی میتونید چک کنید این کاربر مجاز به دیدن جواب این درخواست هست.مثلا تو مثال بالا ID ی کاربر درخواست کننده صفحه نمایش پروفایل با ID ی پروفایل درخواست شده یکیه؟
من سه روشی رو که خودم مطالعه داشتم براتون لیست میکنم اگه شما هم روشی دارید خوشحال میشم آشنا شم.

0-پیش نیاز تمام روش ها
در ASP.NET MVC میتونیم با استفاده از Attribute ی بنام Authorize در سطح Controller دسترسی کاربر لاگین نکرده به این آدرس رو بگیریم.البته این اول راهه چون کاربر لاگین کرده میتونه به آدرس بالا دسترسی داشته باشه و اونو تغییر بده.

[Authorize]
public ActionResult Index(int profileId)

1-ابتدایی ترین کار و البته ضعیف و شکننده ترین کار برسی Referrer هست تا مطمئن بشیم منبع; درخواست وارد شده از وب سایت ماس مثلا کلیک کردن روی یک لینک در صفحه ولی اگه ما همین لینک رو مستقیما در آدرس بار تایپ کنیم چون Referrer مقداری نداره متوجه میشیم که کاربر داره «حمله دستکاری آدرس» انجام میده و…

if (Request.UrlReferrer.Host != "dotnetdev.info")
       {
         RedirectToAction("Error");
       }

چه خوب با کمترین مقدار  کد نویسی مشکل حل شد.(نوچ)
همون کاربر کنجکاوه میدونه که متغییر Referrer در مرورگر به راحتی قابل تغییره و به راحتی با نصب افزونه  RefControl این روش رو دور میزنه.
نکته:این روش محدود به ASP.NET MVC نیست و مثلا در ASP.NET هم قابل استفاده است.

2-همنطور که گفتم بسته به سناریوی ما اگه رابطه ای بین درخواست ارسال شده وکاربر باشه با یه چکینگ ساده تمام مشکلات حله و…

[Authorize]
public ActionResult Index(int profileId)
{
    //روش غلط
    var profile= _dbContext.Profiles.SingleOrDefault(s=>s.Id==profileId); 

    //روش درست
    var profile= _dbContext.Profiles.SingleOrDefault(s=>s.Id==profileId 
           && s.Username==User.Identity.Name
           ); 
}

همنطور که توکد میبینید شرط خوندن پروفایل از دیتابیس نام کاربر رو هم چک میکنه و با اینکار مطمئن میشم کاربر لاگین کرده; فقط و فقط پروفایل خودش رو میتونه ببینه.اگه سناریوی شما و طراحیتون رابطه ی بین درخواست وارد شده و کاربر داشته باشه تقریبا میشه گفتد راه حل نهایی اینه.

3-در مواقعی که نمیتونیم از هویت کاربر استفاده کنیم و به نوعی سناریو و طراحی بشکلی هست که رابطه ی بین درخواست وارد شده و کاربر نیست از این روش استفاده میکنیم.
همنطور که میدونید در ASP.NET MVC قابلیتی بنام AntiForgeryToken برای متدهای از نوع POST وجود داره(برای آشنایی بیشتر این پست جناب مددی رو مطالعه کنید) ولی برای GET به صورت توکار ما چنین قابلیتی نداریم.جناب Albin Abel (همیشه به هندیا بخاطر زبان انگلیسیشون حسودیم میشه)این تکنیک رو برای متدهای GET طراحی کردن.به این شکل که ما همیشه یه پارامتر اضافه بنام Token (به صورت هش شده) داریم که براساس URL ما تهیه میشه و به ته URL اضافه میشه و سمت سرور این Token مجددا تولید و با Token ارسالی مقایسه میشه و…
مثالی رو هم که ایشون مطرح کردن در رابطه با بانک سوالات هست.البته با توجه به رابطه ی بین سوالات و موضوع و کاربر فکرمیکنم از روش دوم هم میشه استفاده کرد.

public static string generateUrlToken(string controllerName, string actionName, ArrayList argumentParams, string password)
{
     string token = "";
     string salt = "#testsalt";
     
     //آماده کردن آدرس 
     string stringToToken = controllerName + "/" + actionName + "/";
     foreach (string param in argumentParams)
        {
         stringToToken += "/" + param;
        }
     
    //آماده کردن توکن و نمک پاش کردن اون 
    byte[] saltValueBytes = System.Text.Encoding.ASCII.GetBytes(salt);
    Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, saltValueBytes);
    byte[] secretKey = key.GetBytes(16);  
    HMACSHA1 tokenHash = new HMACSHA1(secretKey);
    tokenHash.ComputeHash(System.Text.Encoding.ASCII.GetBytes(stringToToken));

    token = Convert.ToBase64String(tokenHash.Hash);
    return token;
}

کار این متد ساختن Token هست که برای امنیت بیشتر نمک پاشش کرده و…

هنگام ساختن لینک در View از متد generateUrlToken استفاده میکنیم.

<%
     ArrayList args = new ArrayList();
     args.Add(Convert.ToString(item.ExamId));
     string token = SecureUrl.Models.SecureUrlToken.generateUrlToken("Questions", "QuestionsView", args, "QuestionList");
%>
 <%: Html.ActionLink("Questions- Secured Link", "QuestionsView", "Questions", new {id = item.ExamId,urltoken=token }, null)%>

و سر آخر قبل از نمایش سوال مربوطه Token ارسالی رو با Token ساخته شده برسی میکنیم و…

public ActionResult QuestionsView(string id, string urltoken)
 {
    //ساخت مجدد توکن
    ArrayList args = new ArrayList();
    args.Add(Convert.ToString(id));
    
    string token = SecureUrlToken.generateUrlToken("Questions", "QuestionsView", args, "QuestionList");
 
   if (token == urltoken)
   {
    //مجاز
   }
 }

نکته 1: که باید توجه کنید با این روش لینک های ما از حالت SEO Friendly URLs در میان.
نکته 2: نسخه جدید این متد (generateUrlToken) از یکه مقدار تصادفی در هر Session برای داینامیک کردن Token های ایجادی استفاده کرده و قابلیتهای دیگه.

نظرات

  1. فرهاد یزدان پناه۱۱/۲۸/۱۳۹۰ ۹:۲۷ بعدازظهر

    من سالهاست از این روش استفاده می کنم.
    البته بیشتر در وبفرم من اونجا یک کنترل به نام SecureHyperLink رو از کنترل HyperLink ارث برده بودم و به صورت خودکار تمام کارها در زمان Render کنترل انجام می شد. و در هر Application_BeginRequest (البته در Page_Init مربوط به MasterPage اصلی بخش مدیریت) کنترل های لازم جهت تطابق کلید امنیتیبا آدرس رو انجام می دادم.
    من از SessionID کاربر، خود آدرس، آدرس وب سایت، ساعت سرور (تاریخ روز با یک سری تغییرات) و یک GUID یکتا به ازای هر با اجرای Application استفاده می کردم.
    خیلی از مسائل رو برای آدم حل می کنه و دیگه لازم نیست آدم زیاد درگیر کنترل های زیاد در اول هر صفحه بشه. چون اگر کاربر تونسته آدرس صحبح رو درخواست کنه حتما قبلا (در زمان تولید آدرس) کنترل شده که آیا به این آدرس دستری دارد یا نه.
    برای MVC به نظرم بهتره که متد ActionLink رو یربار کنیم.

    پاسخحذف
  2.  بسيار عالي...
    فقط يه سوال موقع برسي كردن مجدد GUID ها متفاوت نميشن!؟

    تو نسخه جديد دقيقا همين كارو كرده با استفاده از Extension Methods

    پاسخحذف
  3. فرهاد یزدان پناه۱۱/۲۹/۱۳۹۰ ۳:۱۸ بعدازظهر

    نه GUID در هر بار شروع برنامه (Application_Start) ایجاد و به صورت استاتیک در تمام برنامه ثابت خواهد بود و در تمام درخواست های ارسالی در زمان اجرای آن برنامه همواره ثابت است.
    در مورد User Friendly بودن آدرس ها میشه گفت: نباید بخش بیرونی (عمومی وب سایت) از این روش استفاده کنه چون در این روش ادرس هر صفحه در هر جلسه (Session) کاربر متفاوت است و خیلی مسائل مختلفی رو به وجود می آره.
    این تکنیک بیشتر باید در بخش مدیریتی و بعد از Login مورد استفاده قرار بگیره.

    پاسخحذف

ارسال یک نظر

پست‌های معروف از این وبلاگ

مقدمه ای بر RavenDB – قسمت دوم

مقدمه ای بر RavenDB – قسمت سوم

lnav ابزاری بسیار کاربردی برای پیمایش لاگ ها در لینوکس و البته مک