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 400 错误使用角度 13 和 ASP.NET 核心 5