מניעת CSRF – Cross Site Request Forgery בפיתוח מערכות ASP.NET מבוססות AJAX

17 ביוני 2015

תגיות:
אין תגובות

רבות מן המערכות המפותחות היום מבוססות AJAX, הפעולות שמבצע המשתמש בשכבת הUI מבצעות קריאות ובקשות לצד השרת ע"י Ajax, הטכנולוגיה הנפוצה היום לבצע זאת היא Web API, לצד דרכים נוספות. שימוש בAJAX מאפשר למסך להציג נתונים ולמשתמש לבצע פעולות ללא ביצוע טעינה מחדש של כל הדף, שליחה של חלק מהנתונים לשרת וללא Refresh, בקיצור – יעילות, חווית משתמש טובה והפרדה בין צד הClient לצד השרת.

אחת הבעיות שנובעות מדרך עבודה זו היא היכולת לבצע Cross site request forgery ובמילים אחרות, מאפשרת לגורם A ללא הרשאות לגרום לגורם B – משתמש בעל הרשאות בצע פעולות במערכת מבלי שהתכוון או ידע על כך.

במערכות שהמסכים מבצעים את הפעולות בעצמם בעיה זו אינה קיימת, כיוון שכל פעולת עדכון נתונים חייבת להגיע דרך המסך ולא מתבצעות פעולות מאחורי הקלעים.
במערכות המבוססות AJAX, הפעולות מתבצעות מ"אחורי הקלעים" ע"י קריאה לשירותי Web API, שיטה זו חושפת את האפשרות לבצע פעולות ע"י קריאה ישירה לאותם שירותי Web API ללא צורך במעבר דרך הUI.

בצורה זו, אני יכול לקרוא לכל Web API ולשלוח לו נתונים לפי רצוני. אך, כיוון שהשירותים מוגנים בהרשאות, אני לא אוכל לקרוא לשירותים שאין לי הרשאות אליהם. אין נעקוף מגבלה זו?

ע"י שיטה המזכירה Phishing , אני אוכל ליצור מסך משלי שאינו קשור למערכת ומאחורי הקלעים של המסך שולח את קריאת הWeb API למערכת השניה. כאשר אשלח לינק למסך למשתמש בעל הרשאות חזקות, Admin לדוגמה, כאשר הAdmin יפתח את המסך, תתבצע קריאה לWeb API מהContext שלו, עם ההרשאות החזקות שלו ובשמו. בצורה זו אני בעצם מבצע את הפעולה ביוזמתי אבל בשמו של המשתמש עם ההרשאות החזקות ומבלי שידע על כך.

כיצד ניתן להתגונן בפני CSRF?

במערכות שנעשה שימוש בAJAX, יש לבצע אימות לכל בקשת Web API לכך שהגיע רק ממסכי המערכת ולא בוצעה קריאה ישירה. מקובל לבצע אימות זה ע"י הוספת Token ייחודי לבקשה בזמן טעינת המסך, העברתו בקריאה לWeb API וביצוע אימות לToken בצד השרת.
בצורה זו, כאשר תבוצע קריאה ישירה לWeb API, מנגנון אימות הToken ידחה את הבקשה כיוון שלא הגיע יחד עם Token שנוצר מבקשה שהגיע ממסך.

כאשר אנו מפתחים ב.NET MVC, אין צורך לפתח מנגנון כזה, סביבת הפיתוח נותנת לנו API מובנה המאפשר מניעת CSRF ע"י Token:

שלב א' – יצירת הToken הראשוני בזמן בקשת המסך:

בView המבוקש יש להוסיף את הפקודה Html.AntiForgeryToken() בתוך הForm ששולח את הנתונים, או במידה ומתבצעת קריאת Ajax בכל מקום במסך. לדוגמא:

 

  <button id="postData" name="postData">Transfer Money</button>

התוצאה של פקודה זו היא יצירה של שדה input מסוג hidden שמכיל את הToken הייחודי ובנוסף Cookie שמכיל Token נוסף לאימות.

שלב ב' – ביצוע קריאה מהמסך לשירות הWeb API:

אופציה א': במידה ומתבצעת שליחה ע"י Ajax ישירות לWeb API, דרך Client Script שנמצא בMVC VIEW, יש להוסיף את ערך הToken משדה הhidden ל header של הבקשה ע"י הפקודה @TokenHeaderValue() שתוסיף את ערך הToken באופן אוטומטי:

 

$.ajax("api/values", {

        type: "post",

        contentType: "application/json",

        data: {  }, // JSON data goes here

        dataType: "json",

        headers: {

            'RequestVerificationToken': '@TokenHeaderValue()'

        }

אופציה ב': במידה והקריאה מתבצעת לא מתוך MVC View שמבצע rendering בשרת כגון קובץ JS או מכל client script אחר, יש לקרוא באופן ידני את ערך שדה הhidden ולהוסיפו לHeader:

$.ajax("api/values", {

        type: "post",

        contentType: "application/json",

        data: {  }, // JSON data goes here

        dataType: "json",

        headers: {"X-RVT": $('input[name="__RequestVerificationToken"]').val()}

שלב ג' אימות הבקשה מתוך הWeb API Controller:

שימוש בפקודה AntiForgery.Validate ע"מ לאמת את הToken שנשלח כדי לוודא שהקריאה הגיעה ממסך.

במידה והשתמשנו בשלב ב' באופציה א' הקוד יראה כך:

[Authorize[

public void Post([FromBody]string value)

{

CookieHeaderValue cookie = Request.Headers

  .GetCookies(AntiForgeryConfig.CookieName)

  .FirstOrDefault();

if (cookie != null)

{

  Stream requestBufferedStream = Request.Content.ReadAsStreamAsync().Result;

  requestBufferedStream.Position = 0;

  NameValueCollection myform = Request.Content.ReadAsFormDataAsync().Result;

  try

  {

   AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value, 

    myform[AntiForgeryConfig.CookieName]);

  }

  catch (Exception ex)

  {

   throw new HttpResponseException(

    new HttpResponseMessage(HttpStatusCode.Unauthorized));

  }

}

}

ובמידה ובחרנו בשלב ב' באופציב ב', שורת הValidate תיראה כך:

AntiForgery.Validate(cookie[AntiForgeryConfig.CookieName].Value,

    request.Headers.GetValues("X-RVT").First());

 

בהצלחה

 

הפוסט נכתב ע"י איציק צלף, יועץ בכיר בקבוצת היועצים של מיקרוסופט (MCS )

image

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *