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.