April 8, 2019

How to prevent cross-site request forgery (CSRF) attacks in ASP.Net Web Forms

In this post I will explain how to fix CSRF attack in ASP.Net Web Forms application. I found many articles talking about how to implement this in MVC applications, but I face real trouble to develop this in Web Forms. I found following solution worked for me.

We have to write the following code in site's Master page, and this solution will apply CSRF protection to all content pages that are inherit from this Master page. Also make sure all requests making data modifications must use the ViewState.

Here is the code we need to write in Master Page code-behind file.

 public partial class SiteMaster : MasterPage
 {
   private const string AntiXsrfTokenKey = "__AntiXsrfToken";
   private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
   private string _antiXsrfTokenValue;

   protected void Page_Init(object sender, EventArgs e)
   {
  //check if we already have Anti-XSS cookie, then put it in page's global variable
  var requestCookie = Request.Cookies[AntiXsrfTokenKey];
  Guid requestCookieGuidValue;
  
  if (requestCookie != null
   && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
  {
    _antiXsrfTokenValue = requestCookie.Value;
    Page.ViewStateUserKey = _antiXsrfTokenValue;
  }
  //If we do not found CSRF cookie, then this is a new session, so create a new cookie with Anti-XSRF token
  else
  {
    _antiXsrfTokenValue = Guid.NewGuid().ToString("N");
    Page.ViewStateUserKey = _antiXsrfTokenValue;

    var responseCookie = new HttpCookie(AntiXsrfTokenKey)
    {
   HttpOnly = true,

   //Add the Anti-XSRF token to the cookie value
   Value = _antiXsrfTokenValue
    };

    //If we are using SSL, the cookie should be set to secure 
    if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
    {
   responseCookie.Secure = true;
    }

    //Add the CSRF cookie to the response
    Response.Cookies.Set(responseCookie);
  }

  Page.PreLoad += master_Page_PreLoad;
   }

   protected void master_Page_PreLoad(object sender, EventArgs e)
   {
  //During the initial page request, Add the Anti-XSRF token and user name to the ViewState
  if (!IsPostBack)
  {
    ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
    ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
  }
  //During all post back requests to the page, Validate the Anti-XSRF token
  else
  {
    if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
     || (string)ViewState[AntiXsrfUserNameKey] != (Context.User.Identity.Name ?? String.Empty))
    {
   throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
    }
  }
   }
 }

If Context.User.Identity.Name is empty?

In case if it does not work as per the expectations, one reason could be that the Context.User.Identity.Name is containing empty string not the real user name.

To fix this make sure you need authentication mode set to windowsAuthentication in web.config. and have disabled the anonymous authentication. Hence all the authentication mechanisms are disabled, except for the Windows Authentication.

Another alternative could be if you are manually maintaining login user's data in some Session variable, then replace that statement to read user name from that Session variable rather than Context.User.Identity.Name.

For example, after making this change of reading user name from Session variable, the code listing of master_Page_PreLoad event handler will become similar to this:

  protected void master_Page_PreLoad(object sender, EventArgs e)
   {
  //During the initial page request, Add the Anti-XSRF token and user name to the ViewState
  string userName =  String.Empty;
  if(Session["UserName"] != null)
  {
   userName = Session["UserName"].ToString();
  }
  
  if (!IsPostBack)
  {
    ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
    ViewState[AntiXsrfUserNameKey] = userName;
  }
  //During all post back requests to the page, Validate the Anti-XSRF token
  else
  {
    if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
     || (string)ViewState[AntiXsrfUserNameKey] != userName)
    {
   throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
    }
  }
   }

Thats all you need. Now all the content pages that are inherited from this master page should be able to prevent CSRF attacks.

I hope this helps some of you who get stuck with a similar problem.

No comments:

Post a Comment