防止 asp.net Web 表单中的跨站点请求伪造 (csrf) 攻击

Posted

技术标签:

【中文标题】防止 asp.net Web 表单中的跨站点请求伪造 (csrf) 攻击【英文标题】:preventing cross-site request forgery (csrf) attacks in asp.net web forms 【发布时间】:2015-07-08 11:49:54 【问题描述】:

我使用 Visual Studio 2013 创建了一个 ASP.Net Web 窗体应用程序,并且我使用的是 .NET Framework 4.5。我想确保我的站点不受跨站点请求伪造 (CSRF) 的影响,我发现很多文章都在谈论如何在 MVC 应用程序上实现此功能,但很少有人谈论 Web 表单。在this *** question 上,一条评论指出

"这是一个老问题,但是最新的 Visual Studio 2012 ASP.NET Web 表单模板包括嵌入到主控中的反 CSRF 代码 页。如果您没有模板,这是代码 生成:..."

我的母版页不包含该答案中提到的代码。它真的包含在新应用程序中吗?如果没有,添加它的最佳方法是什么?

【问题讨论】:

Prevent Cross-Site Request Forgery的可能重复 @SilverlightFox,希望不是 ;) 【参考方案1】:

您可以尝试以下方法。在 Web 表单中添加:

<%= System.Web.Helpers.AntiForgery.Gethtml() %>

这将添加一个隐藏字段和一个 cookie。因此,如果您填写一些表单数据并将其发送回服务器,您需要进行简单的检查:

protected void Page_Load(object sender, EventArgs e)

    if (IsPostBack)
        AntiForgery.Validate(); // throws an exception if anti XSFR check fails.

如果反 XSFR 检查失败,AntiForgery.Validate(); 会抛出异常。

【讨论】:

命名空间是 Microsoft.AspNet.WebPages Nuget-Package 中的 System.Web.Helpers。 有人知道是否有不依赖于 Razor 的可用软件包吗? ashx 处理程序怎么样?这效率高吗,我在HTML 页面中有很多ajax 无法将System.Web.Helpers.AntiForgery.GetHtml() 添加到HTML 页面。 我在这里看到link 也必须添加这个标记&lt;input name="__RequestVerificationToken" type="hidden" value="SKZi1uvLbg_G1P-KoK2AJdmeorX1fBgdCbVhLUDim9sk6AFwReVEY6XsuPrvsXJLq5MWOVaGXMnpx09srXkLM_Yjtcfg_4tpc1747jOgo941" /&gt;。有必要吗? 这是否也验证 ajax 调用,例如使用 XMLHttpRequest 完成的调用或由 asp.net 本身完成的调用,例如 ScriptResource.axd?如果没有,那么这将不是 CSRF 攻击的可行解决方案。【参考方案2】:

ViewStateUserKey & 双重提交 Cookie

从 Visual Studio 2012 开始,Microsoft 为新的 Web 表单应用程序项目添加了内置的 CSRF 保护。要使用此代码,请将新的 ASP .NET Web 窗体应用程序添加到您的解决方案并查看 Site.Master 代码隐藏页面。此解决方案将对所有从 Site.Master 页面继承的内容页面应用 CSRF 保护。

此解决方案必须满足以下要求:

所有进行数据修改的 Web 表单都必须使用 Site.Master 页面。 所有进行数据修改的请求都必须使用 ViewState。 该网站必须没有所有跨站点脚本 (XSS) 漏洞。有关详细信息,请参阅如何使用 Microsoft .Net Web 保护库修复跨站点脚本 (XSS)。

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)
  
    //First, check for the existence of the Anti-XSS cookie
    var requestCookie = Request.Cookies[AntiXsrfTokenKey];
    Guid requestCookieGuidValue;

    //If the CSRF cookie is found, parse the token from the cookie.
    //Then, set the global page variable and view state user
    //key. The global variable will be used to validate that it matches 
    //in the view state form field in the Page.PreLoad method.
    if (requestCookie != null
        && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
    
      //Set the global token variable so the cookie value can be
      //validated against the value in the view state form field in
      //the Page.PreLoad method.
      _antiXsrfTokenValue = requestCookie.Value;

      //Set the view state user key, which will be validated by the
      //framework during each request
      Page.ViewStateUserKey = _antiXsrfTokenValue;
    
    //If the CSRF cookie is not found, then this is a new session.
    else
    
      //Generate a new Anti-XSRF token
      _antiXsrfTokenValue = Guid.NewGuid().ToString("N");

      //Set the view state user key, which will be validated by the
      //framework during each request
      Page.ViewStateUserKey = _antiXsrfTokenValue;

      //Create the non-persistent CSRF cookie
      var responseCookie = new HttpCookie(AntiXsrfTokenKey)
      
        //Set the HttpOnly property to prevent the cookie from
        //being accessed by client side script
        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 to
      //prevent it from being sent over HTTP connections
      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 load, add the Anti-XSRF token and user
    //name to the ViewState
    if (!IsPostBack)
    
      //Set Anti-XSRF token
      ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;

      //If a user name is assigned, set the user name
      ViewState[AntiXsrfUserNameKey] =
             Context.User.Identity.Name ?? String.Empty;
    
    //During all subsequent post backs to the page, the token value from
    //the cookie should be validated against the token in the view state
    //form field. Additionally user name should be compared to the
    //authenticated users name
    else
    
      //Validate the Anti-XSRF token
      if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
          || (string)ViewState[AntiXsrfUserNameKey] !=
               (Context.User.Identity.Name ?? String.Empty))
      
        throw new InvalidOperationException("Validation of " +
                            "Anti-XSRF token failed.");
      
    
  

Source

【讨论】:

我正在使用 VS2017,但在 Site.Master.cs(全新的 Web 表单应用程序项目)中没有看到此代码。 我们使用这段代码大约一年,一直遇到一个问题:偶尔抛出异常。我们有我们的自定义身份验证模块来保存身份验证(长期存在的)cookie。当用户打开浏览器并导航到该站点时,它将根据该 cookie 自动登录。有一次我很幸运至少打断点并查看哪个条件失败:这是第二学期。 Context.User.Identity.Name 为空,但 ViewState 包含用户名。为什么我们还要填充和检查用户名? 也许我遗漏了一些东西,但似乎攻击者可以通过查看当前页面的源代码并复制“__VIEWSTATE”和“__VIEWSTATEGENERATOR”隐藏输入,然后重新提交来轻松绕过此代码.实际上,我自己能够成功地做到这一点。您如何防止这种情况发生? @ryanulit XSRF 背后的想法是有人可以伪装成来自完全不同域的客户端(例如通过 有谁知道这个代码在 vb.net 中的等价物是什么?我对事件线有疑问。【参考方案3】:

当您在 VS 2013 中创建新的“Web 表单应用程序”项目时,site.master.cs 将自动在课程的 Page_Init 部分中包含 XSRF/CSRF 代码。如果还是没有得到生成的代码,可以手动Copy+Paste代码。如果您使用的是 C#,请使用以下代码:-

private const string AntiXsrfTokenKey = "__AntiXsrfToken";
private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
private string _antiXsrfTokenValue;

 protected void Page_Init(object sender, EventArgs e)
    
        // The code below helps to protect against XSRF attacks
        var requestCookie = Request.Cookies[AntiXsrfTokenKey];
        Guid requestCookieGuidValue;
        if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
        
            // Use the Anti-XSRF token from the cookie
            _antiXsrfTokenValue = requestCookie.Value;
            Page.ViewStateUserKey = _antiXsrfTokenValue;
        
        else
        
            // Generate a new Anti-XSRF token and save to the cookie
            _antiXsrfTokenValue = Guid.NewGuid().ToString("N");
            Page.ViewStateUserKey = _antiXsrfTokenValue;

            var responseCookie = new HttpCookie(AntiXsrfTokenKey)
            
                HttpOnly = true,
                Value = _antiXsrfTokenValue
            ;
            if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
            
                responseCookie.Secure = true;
            
            Response.Cookies.Set(responseCookie);
        

        Page.PreLoad += master_Page_PreLoad;
    

    protected void master_Page_PreLoad(object sender, EventArgs e)
    
        if (!IsPostBack)
        
            // Set Anti-XSRF token
            ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
            ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
        
        else
        
            // Validate the Anti-XSRF token
            if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
                || (string)ViewState[AntiXsrfUserNameKey] != (Context.User.Identity.Name ?? String.Empty))
            
                throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
            
        
    

【讨论】:

是的,我正在使用 C#,我尝试复制并粘贴此函数,但它似乎有多个错误。你确定这是自动生成的代码吗? 无法识别 :AntiXsrfTokenKey in Request.Cookies[AntiXsrfTokenKey];并且也无法识别这些变量 _antiXsrfTokenValue, master_Page_PreLoad @NadaNaeem 我已经包含了缺失的常量。 谢谢,但仍然缺少master_Page_PreLoad,但没关系 这里有个小bug。假设应用程序是新启动的并且没有 antixsrf cookie。用户同时发出 2 个请求(第二个请求在第一个请求结束之前开始)将为第一个请求生成一个令牌,并将其写入 antixsrf cookie。 "requestCookie != null" 将为第二个请求返回 false,因为它开始时客户端上没有 cookie。因此将生成另一个新的 antixsrf 令牌并将其写入 cookie。当第一个请求回发时,它会得到一个 xsrf 错误。因为 antixsrf cookie 值被第二个请求改变了。【参考方案4】:

你可以使用下面的代码,它会检查请求来自哪里

if ((context.Request.UrlReferrer == null || context.Request.Url.Host != context.Request.UrlReferrer.Host)) 
    
        context.Response.Redirect("~/error.aspx", false);
    

它对我很有用!!!

【讨论】:

这是错误和危险的。它仅将 CSRF 限制在同一主机上,并且如果剥离了 referer 标头,则可能会破坏真实请求,这是相对常见的。

以上是关于防止 asp.net Web 表单中的跨站点请求伪造 (csrf) 攻击的主要内容,如果未能解决你的问题,请参考以下文章

asp.net signalr core 中的跨域请求不起作用?

如何使用 Angular 2 在 POST 上启用 ASP.NET MVC 4 中的跨源请求

防止 ASP.NET 中的 413 错误请求实体太大 [重复]

ASP.NET如何防止SQL注入

防止来自静态站点的跨站点脚本攻击

基于.Net Framework 4.0 Web API开发:ASP.NET Web APIs AJAX 跨域请求解决办法(CORS实现)