در فریم ورک ASP.NET Core فرایند استخراج اطلاعات مورد نیاز از درخواست HTTP و تبدیل آن‌ها به اشیا توسط Model Binder انجام می‌شود. هرچند این امکان بسیار کاربردی و جذاب به نظر می‌رسد، استفاده نادرست از آن منجر به وقوع آسیب پذیری‌ Mass Assignment می‌شود.

در این مقاله با آسیب پذیری Mass Assignment یا Overposting آشنا شده و روش‌هایی را برای جلوگیری از آن می‌آموزیم. این آسیب پذیری بسیار رایج هست. تا حدی که در گذشته GitHub نیز نسبت به آن آسیب پذیر واقع شده!

Mass Assignment چه زمانی رخ می‌دهد؟

این آسیب پذیری هنگام انجام فرآیند Model Binding رخ می‌دهد. Razor Pages ، MVC Controller، Minimal Api و حتی Blazor از Model Binder برای استخراج اطلاعات موجود در درخواست HTTP و تبدیل آن‌ها به اشیا قابل استفاده در کد استفاده می‌کنند. در این مقاله، پروژه‌ای از نوع Blazor SSR (معرفی شده در دات نت ۸) را به عنوان نمونه استفاده می‌کنیم. برای مشاهده سورس کد از این لینک استفاده کنید.

بررسی پروژه

در این پروژه یک کلاس ساده به شکل زیر موجود هست :

public class Person
{
	public string? Name { get; set; }
	public string? Family { get; set; }
	public bool? IsAdmin { get; set; }
}

در صفحه Index یک فرم ساده وجود دارد که مقادیر نام و نام خانوادگی را از کاربر دریافت می‌کند :

<EditForm Model="PersonToAdd" OnValidSubmit="HandleValidSubmit" FormName="AddPerson">
	<div class="form-group m-3">
		<label for="name">Name: </label>
		<InputText @bind-Value="PersonToAdd.Name" />
	</div>
	<div class="form-group m-3">
		<label for="family">Family: </label>
		<InputText @bind-Value="PersonToAdd.Family" />
	</div>

	<div class="form-group">
		<button type="submit" class="btn btn-primary" @onclick="@HandleValidSubmit">Submit</button>
	</div>
</EditForm>

احتمالا تا الان متوجه شده باشید که در این فرم پراپرتی IsAdmin کلاس Person دریافت نمی‌شود و در نظر گرفته نشده است. پس مقدار پیشفرض این پراپرتی یعنی false استفاده می‌شود.

در همین صفحه با استفاده از Quick Grid مقادیر وارد شده در فرم را نمایش می‌دهیم :

<QuickGrid Items="@PersonList.AsQueryable()">
	<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
	<PropertyColumn Property="@(p => p.Family)" Sortable="true" />
	<PropertyColumn Property="@(p => p.IsAdmin)" Sortable="true" />
</QuickGrid>

بخش code این صفحه به شکل زیر هست :

@code {

	[SupplyParameterFromForm]
	public Person PersonToAdd { get; set; } = new();


	private void HandleValidSubmit()
	{
		PersonList.Add(PersonToAdd);
		PersonToAdd = new();
	}


	public class Person
	{
		public string? Name { get; set; }
		public string? Family { get; set; }
		public bool? IsAdmin { get; set; }
	}
}

پراپرتی PersonList از سیستم تزریق وابستگی دریافت می‌شود :

@inject List<Person> PersonList

که در کلاس Program به شکل زیر تعریف شده :

builder.Services.AddSingleton(new List<Index.Person>());

در صورتی که پروژه را اجرا کنید و فرم موجود در صفحه Home را پر کنید، مقادیر ورودی در لیست زیر فرم نمایش داده می‌شود.

Simple Blazor Formهمانطور که در تصویر هم مشخص هست مقدار IsAdmin برابر False هست و نمایش داده نمی‌شود. اگرچه امکان وارد کردن مقدار IsAdmin در فرم ممکن نیست، همواره احتمال دستکاری ترافیک توسط کاربر وجود دارد.

بررسی آسیب پذیری

برای مثال از Burp Suite برای دستکاری ترافیک استفاده می‌کنیم. به تصویر زیر توجه کنید :

Burp Suite Repeaterهمانطور که در تصویر مشخص هست، با استفاده از امکان Repeater در Burp Suite یک درخواست به سمت برنامه ارسال می‌کنیم. اما در این درخواست مقدار IsAdmin رو برابر True قرار دادیم. در سمت سرور (Blazor SSR) این مقدار به پراپرتی IsAdmin کلاس Person بایند شده و هنگام نمونه سازی مقدار True استفاده می‌شود. اکنون اگر به لیست موجود در صفحه Home نگاه کنیم :

Mass Assignmentمی‌بینیم یک ردیف به این لیست اضافه شده اما مقدار IsAdmin به شکل غیر منتظره‌ای برابر True هست! در اینجا با ساده‌ترین حالت آسیب پذیری Mass Assignment آشنا شدیم!

در واقع برنامه نویس قصد ندارد مقدار IsAdmin را از کاربر دریافت کند. بنابراین در فرم، فیلدی برای این مقدار در نظر نگرفته. سناریویی را فرض کنید که کاربر پس از ثبت نام، در صورت تایید ادمین، به پنل ادمین دسترسی خواهد داشت و تایید ادمین در قالب IsAdmin = true انجام می‌شود. با وجود این آسیب پذیری، کاربر بدون تایید ادمین می‌تواند به پنل ادمین دسترسی داشته باشد! Model Binder در این مثال تمامی مقادیر موجود در درخواست HTTP را به مقادیر کلاس مربوطه یعنی Person بایند می‌کند. در صورتی که مقدار IsAdmin نباید Bind شود. به این شکل آسیب پذیری رخ می‌دهد!

جلوگیری از Mass Assignment در ASP.NET Core

برای جلوگیری از این آسیب پذیری روش‌های زیادی وجود دارد. در ادامه به معرفی برخی از این روش‌ها می پردازیم.

استفاده از ViewModel یا DTO

جدا سازی Model یکی از ساده ترین روش‌های جلوگیری از Mass Assignment هست. می‌توانیم کلاسی جدید شبیه به کلاس Person تعریف کنیم با این تفاوت که پراپرتی IsAdmin در آن وجود ندارد. در این صورت حتی اگر ترافیک توسط کاربر دستکاری شود و پارامتر IsAdmin درون درخواست اضافه شود، Model Binder آن را در نظر نمی‌گیرد. زیرا پراپرتی متناظر با آن وجود ندارد.

public class PersonViewModel
{
	public string? Name { get; set; }
	public string? Family { get; set; }
}

سپس از این کلاس در Action Method ، Page Handler ، Minimal api ، Blazor و هر جایی که Model Binder درگیر هست استفاده می‌کنیم.

از معایب این روش می‌توان به این مورد اشاره کرد که Data Annotation‌ها و … در هر دو کلاس باید پیاده سازی شوند. هرچند راه حل ساده‌ای برای رفع این عیب وجود دارد : Inheritance ! به مثال زیر دقت کنید. Base Clsss :

public class Person
{
        [Required]
        [MaxLength(150)]
	public string? Name { get; set; }

        [MaxLength(150)]
	public string? Family { get; set; }
}

و :

public class PersonWithIsAdmin : Person
{
	public bool? IsAdmin { get; set; }
}

استفاده از Attribute‌های مختلف

در MVC Controller یا Razor Pages می‌توانید از BindAttribute به شکل زیر استفاده کنید :

public IActionResult SomeAction([Bind(nameof(Model.PropertyToBind))] Model input)
{
    return View("Index", input);
}

این Attribute کمک می‌کند تنها پراپرتی های مورد نیاز، در فرآیند Model Binding شرکت داشته باشند و سایر پراپرتی‌ها در نظر گرفته نمی‌شوند. مشکل این روش زمانی هست که تعداد پراپرتی‌ها زیاد باشد!

بجای مورد بالا می‌توانیم از Data Annotation‌ها در Model مربوطه استفاده کنیم. برای مثال از [Editable] یا [BindNever] استفاده می‌کنیم :

public class Person
{
	public string? Name { get; set; }
	public string? Family { get; set; }

        [BindNever]
	public bool? IsAdmin { get; set; }
}

 

منابع و مطالعه بیشتر

سورس کد پروژه

Public Key Security Vulnerability and Mitigation – The GitHub Blog

Egor Homakov: Hacking rails/rails repo

Mass Assignment – OWASP Cheat Sheet Series

Preventing mass assignment or over posting in ASP.NET Core (andrewlock.net)

ASP.NET – Overposting/Mass Assignment Model Binding Security – Scott Hanselman’s Blog

How to prevent mass assignment in ASP.NET Core – SoftDevPractice