ValidateAntiForgeryToken 端点属性在 Asp.Net 核心 Angular Web 应用程序中每次 CSRF 攻击的使用情况

Posted

技术标签:

【中文标题】ValidateAntiForgeryToken 端点属性在 Asp.Net 核心 Angular Web 应用程序中每次 CSRF 攻击的使用情况【英文标题】:ValidateAntiForgeryToken endpoint attribute usage in Asp.Net core Angular web app per CSRF attack 【发布时间】:2021-11-04 13:54:06 【问题描述】:

我有一个使用 asp.net core (3.1) 后端和 angular 前端 (8.2.11) 的 Web 应用程序。它使用 asp.net Identity 框架进行用户身份验证。它将身份验证令牌存储在本地存储中,以用作请求中的身份验证标头。一切都在感觉控制器端点只能在用户登录时访问,如果退出,直接在浏览器中输入端点将被拒绝。

我仍然不确定这样的设置是否能防止跨站请求伪造 (XSRF/CSRF) 攻击。我知道使用 cookie 存储身份验证令牌容易受到 CSRF 的影响,我在某些端点上尝试了 [ValidateAntiForgeryToken] 属性,它当然破坏了这些端点。我知道在 Razor 页面中,表单会自动注入防伪令牌。那么,我需要在我的角度前端进行设置吗?如果是,如何? (我在网上搜索了一下,说明到处都是,非常混乱,没有明确的共识)。

【问题讨论】:

【参考方案1】:

步骤 1

将中间件添加到生成 AntiforgeryToken 的中间件管道中,并将令牌嵌入到附加到响应的非 http-only cookie 中:

public class Startup

    public void ConfigureServices(IServiceCollection services)
    
        ...
        services.AddAntiforgery(options => 
            options.HeaderName = "X-XSRF-TOKEN";
        );
    
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
    
        ...;
        app.Use((context, next) => 
            var tokens = antiforgery.GetAndStoreTokens(httpContext);
            httpContext.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions()  Path = "/", HttpOnly = false );
        );
    

我为此创建了 a little package,其中包含此中间件。

第二步

将您的 Angular 应用配置为通过 javascript 读取非 http-only cookie (XSRF-TOKEN) 的值,并将该值作为X-XSRF-TOKEN 标头传递给HttpClient 发送的请求:

@NgModule(
  declarations: [...],
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions(
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-XSRF-TOKEN'
    ),
    ...
  ],
  providers: [...],
  bootstrap: [AppComponent]
)
export class AppModule  

第三步

现在您可以使用[ValidateAntiforgeryToken] 属性来装饰您的控制器方法:

[ApiController]
[Route("web/v1/[controller]")]
public class PersonController : Controller

    private IPersonService personService;
    public PersonController(IPersonService personService)
    
        this.personService = personService;
    
    
    [HttpPost]
    [Authorize]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult<Person>> Post([FromBody] Person person)
    
        var new_person = await personService.InsertPerson(person);
        return Ok(new_person);
    

第四步

确保您发送的请求具有以下类型的 url as stated here:

/my/url //example.com/my/url

错误的网址:

https://example.com/my/url

注意

我使用 Identity Cookie 身份验证:

services.AddAuthentication(/* No default authentication scheme here*/)

由于 ASP.NET Core Authentication 中间件只处理 XSRF-TOKEN 标头,而不是 X-XSRF-TOKEN cookie,因此您不再容易受到跨站点请求伪造的影响。

剧透

您会注意到,在登录/退出后,发送的第一个 Web 请求仍将被 XSRF 保护阻止。这是因为身份在 web 请求的生命周期内不会改变。所以当发送Login webrequest 时,响应会附加一个带有 csrf-token 的 cookie。但是这个令牌仍然是使用您尚未登录时的身份生成的。

发送Logout webrequest 的计数相同,响应将包含一个带有 csrf-token 的 cookie,就像您仍然登录一样。

要解决此问题,您只需在每次登录/退出时发送另一个实际上什么都不做的网络请求。在此请求期间,您将再次拥有正确的身份以生成 csrf-token。

logoutClicked() 
  this.accountService.logout().then(() => 
    this.accountService.csrfRefresh().then(() => 
      this.activeUser = null;
    );
  ).catch((error) => 
    console.error('Could not logout', error);
  );

登录也一样

this.accountService.login(this.email, this.password).then((loginResult) => 
  this.accountService.csrfRefresh().then(() => 
    switch (loginResult.status) 
      case LoginStatus.success:
        this.router.navigateByUrl(this.returnUrl);
        this.loginComplete.next(loginResult.user);
        break;
      default:
        this.loginResult = loginResult;
        break;
    
  );
);

csrfRefresh方法的内容

public csrfRefresh() 
  return this.httpClient.post(`$this.baseUrl/web/Account/csrf-refresh`, ).toPromise();

服务器端

[HttpPost("csrf-refresh")]
public async Task<ActionResult> RefreshCsrfToken()

    // Just an empty method that returns a new cookie with a new CSRF token.
    // Call this method when the user has signed in/out.
    await Task.Delay(5);

    return Ok();

This is where I login the user in my own app

【讨论】:

刚刚尝试了您的解决方案,现在所有端点响应都设置了XSRF-TOKEN,但是,我用[ValidateAntiForgeryToken] 装饰的端点失败并返回400。是否需要[授权]?另外,Path='/' 是否必要。 我检查了 chrome,在 Cookies 下,它有 XSRF-TOKEN.AspNetCore.Identity.Application.AspNeCore.Antiforgery.rQWsmpBETsk 条目。这些相同的条目也出现在该失败请求的标头中。我认为请求标头应该有启动中指定的X-XSRF-TOKEN,但它有XSRF-TOKEN,我是否必须在请求标头中手动设置它,即X-XSRF-TOKEN 不,您拥有的 cookie 的名称是正确的。通常,第 2 步应确保在请求标头中发送 X-XSRF-TOKEN 标头。您能在 Network 选项卡中看到正在发送的标头吗?您能在 Output 窗口中看到错误吗? 是的,我确实通过检查检查了“网络”选项卡,请求标头中没有 X-XSRF-TOKEN 标头条目,只有上面列出的 3 个。唯一的“错误”是 400 响应失败,没有其他错误。我在某处读到 Angular 不会自动添加该标题条目。所以,我想知道我是否需要手动附加到 http 请求上?我正在研究... 是的。在我的 app.module.ts 中:import HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS from '@angular/common/http'; 和导入:HttpClientXsrfModule.withOptions( cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' ),【参考方案2】:

Angular 提供了默认启用的内置反 CSRF/XSRF 保护。

Angular 的HttpClient 内置了对这种技术的客户端部分的支持。在HttpClient guide了解更多信息

请注意,在 HttpClient 上默认启用 CSRF/XSRF 保护,但仅当后端在用户身份验证时使用随机值设置名为 XSRF-TOKEN 的 cookie 时才有效。

【讨论】:

我从那个HttpClient 指南中读到,它说:默认情况下,拦截器会在所有变异请求(例如 POST)上将此标头发送到相对 URL,但不会在 GET/HEAD 请求或带有绝对 URL 的请求。因此,这似乎表明默认情况下,Angular 不会在大多数 http 请求中插入该防伪标头条目,但相对 URL 上的 POST 除外。我很惊讶它没有提供手动附加该标题条目的方法,或者是吗?我很困惑。

以上是关于ValidateAntiForgeryToken 端点属性在 Asp.Net 核心 Angular Web 应用程序中每次 CSRF 攻击的使用情况的主要内容,如果未能解决你的问题,请参考以下文章

如何全局设置 ValidateAntiForgeryToken

Web API 和 ValidateAntiForgeryToken

ValidateAntiforgeryToken 失败

ValidateAntiForgeryToken 400 错误使用角度 13 和 ASP.NET 核心 5

如何正确使用ValidateAntiForgeryToken?

带有 SPA 架构的 ValidateAntiForgeryToken