در این جلسه به بررسی حمله Cross-Site و آموزش روش جلوگیری از این حمله در ASP.NET Core خواهیم پرداخت . به این نوع حمله که با عنوان XSRF/CSRF Attacks نیز شناخته میشود، در واقع ارسال درخواست مشکوک و مخرب به وب اپلیکشن مورد نظر (مانند اپلیکشن بانک و یا پرتالهای حاوی اطلاعات مالی) از سوی یک وب اپلیکیشن یا صفحه ثالث توسط مرورگر مورد اعتماد وب اپلیکیشن مورد نظر گفته میشود. در این نوع حمله، وب سایت و یا وب اپلیکشن مخرب از احراز هویت قبلی کاربر در پرتال مورد هدف جهت برقراری و ثبت درخواست استفاده میکند.
حمله Cross-Site چیست؟
حمله Cross-Site و یا XSRF/CSRF Attacks به ارسال درخواست مخرب به وب اپلیکشن مورد نظر توسط وب اپلیکشن ثالث از طریق مرورگر مورد اعتماد وب اپلیکیشن هدف گفته میشود. این حمله درخواست مشکوک را با استفاده از احراز هویت قبلی کاربر توسط مرورگر مورد اعتماد وب اپلیکیشن هدف ثبت میکند.
برای توضیح بیشتر، شما فرض کنید که یک ایمیل با متن زیر دریافت میکنید.
حال با توجه به جذاب بودن موضوع، کاربر ممکن است وسوسه گردد و برای دریافت جایزه خود بروی کلید مربوطه کلیک نماید. و این در واقع زمانی است که حمله Cross-Site صورت میپذیرد.
چرا که در صورتی که قبلا با توجه به این مثال در پرتال بانکی خود احراز هویت کرده باشید، کد زیر که در واقع پشت ظاهر و پیام جذاب مخفی شده است، اجرا میگردد. در واقع صفحه فوق حاوی کد زیر میباشد.
1 2 3 4 5 6 7 8 9 10 11 |
<h1>Congratulations! You're a Winner!</h1> <form action="https://yourbank.com/transffer" method="post"> <input type="hidden" name="Transaction" value=“transfer" /> <input type="hidden" name=“Receiver" value=“4154-45445-4444" /> <input type="hidden" name="Amount" value=“999999" /> <input type="submit" value="Click to collect your prize!" /> </form> |
و همانگونه که مشاهده میکنید با کلیک برروی دکمه مورد نظر، درخواست انتقال پول از حساب شما به حساب مقصد که متعلق به شخص حمله کننده میباشد با مبلغ ذکر شده ثبت میگردد. البته ما برای درک سادهتر این حمله یک مثال ساده ذکر نمودیم و در واقع موضوع میتواند بسیار پیچیدهتر باشد.
جلوگیری از حمله Cross-Site در ASP.NET Core
برای جلوگیری از XSRF/CSRF Attacks روشهای متفاوتی وجود دارد و روش ارائه شده در این جلسه، تنها راه کافی برای تامین امنیت وب اپلیکیشن شما نخواهد بود. ما در این جلسه برای جلوگیری از حمله Cross-Site از توکن Anti Forgery استفاده میکنیم. این توکن به صورت پیشفرض از طرف فرمهای درون View با متد Post در ASP.NET Core به کنترلر ارسال میگردد. به عبارت دیگر به صورت پیشفرض مقدار تگ هلپر asp-antiforgery در فرمهای درون نما، برابر با True میباشد.
همچنین برای جلوگیری از این حمله در کنترلر، برای اکشن متدهای روش Post میتوانیم از خصوصیت ValidateAntiForgeryToken استفاده نماییم.
ما درون وب اپلیکیشن مربوط به این دوره آموزشی، برای ایمنسازی آن نسبت به حمله Cross-Site از خصوصیت (Attribute) ذکر شده استفاده نمودیم. کد زیر مربوط به کنترلر مورد نظر میباشد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
namespace DailyCostWebApplication.Controllers { public class CostController : Controller { private readonly ICostRepository costRepository; private readonly ICategoryRepository categoryRepository; private readonly IWebHostEnvironment webHostEnvironment; public CostController(ICostRepository _costRepository, ICategoryRepository _categoryRepository, IWebHostEnvironment _webHostEnvironment) { costRepository = _costRepository; categoryRepository = _categoryRepository; webHostEnvironment = _webHostEnvironment; } [HttpGet] public IActionResult Create() { LoadDropdownList(); return View(); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Create(CreateCostViewModel model) { if (ModelState.IsValid) { Cost cost = new() { Amount = model.Amount, Comment = model.Comment, RegisteredDate = model.RegisteredDate, CategoryID = model.CategoryID, PaymentMethod = model.PaymentMethod }; if(model.UploadFile != null) { cost.InvoiceImagePath = UploadFile(model.UploadFile); } costRepository.Create(cost); return RedirectToAction("Index"); } LoadDropdownList(); return View(model); } [HttpGet] public IActionResult Index(string searchby, string searchfor, int? page, string sortby) { var costs = costRepository.GetCostList(searchby, searchfor,sortby).ToPagedList(page ?? 1, 5); return View(costs); } [HttpGet] public IActionResult Detail(int id) { var cost = costRepository.GetCostByID(id); return View(cost); } [HttpGet] public IActionResult Update(int id) { var cost = costRepository.GetCostByID(id); UpdateCostViewModel model = new() { ID = cost.ID, Amount = cost.Amount, RegisteredDate = cost.RegisteredDate, Comment = cost.Comment, CategoryID = cost.CategoryID, PaymentMethod = cost.PaymentMethod, ExsitingFile = cost.InvoiceImagePath }; LoadDropdownList(); return View(model); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Update(UpdateCostViewModel model) { if (ModelState.IsValid) { Cost UpdatedCost = costRepository.GetCostByID(model.ID); UpdatedCost.Amount = model.Amount; UpdatedCost.RegisteredDate = model.RegisteredDate; UpdatedCost.Comment = model.Comment; UpdatedCost.CategoryID = model.CategoryID; UpdatedCost.PaymentMethod = model.PaymentMethod; if(model.UploadFile != null) { if(UpdatedCost.InvoiceImagePath != null) { string ExitingFile = Path.Combine(webHostEnvironment.WebRootPath, "images", UpdatedCost.InvoiceImagePath); System.IO.File.Delete(ExitingFile); } UpdatedCost.InvoiceImagePath = UploadFile(model.UploadFile); } costRepository.Update(UpdatedCost); return RedirectToAction("Index"); } LoadDropdownList(); return View(model); } [ValidateAntiForgeryToken] [HttpPost] public IActionResult Delete(int id) { Cost cost = costRepository.GetCostByID(id); if (cost.InvoiceImagePath != null) { string ExitingFile = Path.Combine(webHostEnvironment.WebRootPath, "images", cost.InvoiceImagePath); System.IO.File.Delete(ExitingFile); } costRepository.Delete(id); return RedirectToAction("index"); } private void LoadDropdownList() { var Categories = categoryRepository.GetAllCategories(); List<SelectListItem> CatList = new(); CatList.Add(new SelectListItem("Select a Category", "-1")); foreach (var category in Categories) { CatList.Add(new SelectListItem(category.CategoryName, category.ID.ToString())); } List<SelectListItem> PaymentList = new(); PaymentList.Add(new SelectListItem("Select a Payment Method", "")); var PaymentMethod = Enum.GetValues(typeof(PaymentMethods)).Cast<PaymentMethods>().ToList(); for (var i = 0; i < PaymentMethod.Count(); i++) { PaymentList.Add(new SelectListItem(PaymentMethod[i].ToString(), i.ToString())); } ViewBag.Categories = CatList; ViewBag.PaymentMethods = PaymentList; } private string UploadFile(IFormFile formFile) { string UniqueFileName = Guid.NewGuid().ToString() + "-" + formFile.FileName; string TargetPath = Path.Combine(webHostEnvironment.WebRootPath, "images", UniqueFileName); using (var stream = new FileStream(TargetPath, FileMode.Create)) { formFile.CopyTo(stream); } return UniqueFileName; } } } |
و همانطور که مشاهده میکنید برای تمامی اکشن متدهای نوع Post از خصوصیت ValidateAntiForgeryToken استفاده نمودیم.
در صورت نیاز به جزئیات بیشتر، میتوانید ویدئو آموزشی این جلسه را تماشا نمایید. همچنین برای آگاهی از جلسات بعدی این دوره آموزشی، ما را در اینستاگرام، تلگرام، یوتیوب و آپارات دنبال کنید. ضمنا لیست کامل جلسات در این قسمت در دسترس شما میباشد و سورس کد این جلسه را میتوانید از GitHub ما دانلود نمایید.
دانلود اسلایدهای آموزشی این جلسه از اینجا