aspnet / Antiforgery

[Archived] AntiForgery token feature for ASP.NET Core. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
78 stars 40 forks source link

Discussion: Smarter defaults for Antiforgery #66

Closed rynowak closed 7 years ago

rynowak commented 8 years ago

_Use this issue for feedback, questions and discussions related to https://github.com/aspnet/Announcements/issues/156 _


Update 3/30

We're not 100% satisfied with what we built here, so we're not going to make any changes to the default experience for validating tokens in RC2.

We're not going to ship the AntiforgeryMiddleware because we're not confident that it's our long term approach. We're still going to be looking at a solution that works with middleware, with the authentication system and can protect more of your application than just MVC.

In MVC - the @Html.BeginForm and <form> taghelper will still generate an antiforgery token by default. The [AutoValidateAntiforgeryToken] filter will be shipped in RC2 and will be available for you to use, but will not be configured by default.

Below is the original text of the announcement, but we're not making any high impact changes here for RC2.

Summary

We're making some improvements in RC2 to the Antiforgery package package to provide more sensible defaults and support usage outside of MVC. The Antiforgery package helps your protect against CSRF (also known as XSRF) vulnerabilities by requiring a server-generated token in the request.

CSRF protection is needed when a browser can make logged-in requests on a user's behalf through a form, or an image tag. A malicious website can craft a tag which will submit a request to a trusted website. This can occur when using a persistent authentication mechanism like cookie, windows-auth or basic.

With these changes we're going to detail a few strategies that can work for protecting your site from CSRF while reducing the amount of boilerplate.

History

Historically, CSRF protection has been available in MVC applications through the @Html.AntiForgeryToken() helper and [ValidateAntiForgeryToken] attribute. A developer would call @Html.AntiForgeryToken() inside every <form> element, and slap on a [ValidateAntiForgeryToken] attribute on every action method that handles user input. This boilerplate could also be generated by scaffolding, so if you were using the tools it would be done for you.

This Razor Code:

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    <input type="submit" value="Submit" />
}

Yields:

<form action="/" method="post">
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Huz....">
    <input type="submit" value="Submit">
</form>

And is validated by:

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult HandleInput()
{
    ...
}

Uh oh

The problem with this approach of course is that you need to know about it in order to be protected. You also have to remember to put these calls on both ends (the form, and the handling action).

Changes

The first step towards improving the status quo is to make it automatic. We've made the <form> TagHelper and @Html.BeginForm include a token by default when the action is anything other than GET. You can suppress the token if you have a scenario where you don't want it.


We've also added a smarter version of [ValidateAntiForgeryToken] - [AutoValidateAntiforgeryToken].

This new attribute is a filter that expect you to apply at the global level (your whole site). The filter validates a token for unsafe (DELETE, PUT, POST, ...) HTTP methods, and does not attempt to validate a token for safe HTTP methods (GET, HEAD, OPTIONS, TRACE).

See the recommendations below. We strongly discourge making any mutations to your site's state for HTTP methods like GET.


We've also added a middleware, which plugs into the authentication system. Any request using an unsafe (DELETE, PUT, POST, ...) HTTP method will not be considered authentic unless it includes a valid token. That is, the user is not considered "logged in" unless a valid token is provided.

The advantage of using middleware is that it makes it possible to support both cookie auth (with antiforgery) and bearer-token auth (without antiforgery) on the same endpoints if desired.


Using [ValidateAntiForgery] of course still works.

Recommendations

First of all make sure that your site doesn't mutate state on any safe HTTP methods (GET, HEAD, OPTIONS, TRACE). This is a general bad practice to avoid, and can be a source of CSRF vulnerabilities.

Secondly, look for endpoints that allow anonymous user input. These are generally things like a "login" or "register" action. Place [ValidateAntiForgeryToken] on these actions if using MVC, otherwise call IAntiforgery.ValidateRequestAsync. Our templated versions of this code for user management always include [ValidateAntiForgeryToken]. This will work with any strategy.


From here consider one of the following options:

Using Antiforgery Middleware

This is the strategy that our templates (with authentication) use. The UseAntiforgery() must come after authentication middleware that you want to protect.

Requests without a valid token for a "logged in" user will be treated by the auth system as "not logged in". This might mean a challenge, login screen, or unauthorized response.

public void Configure(IApplicationBuilder app)
{
    app.UseIdentity();

    // Require a token on POST/PUT/DELETE/ETC for logged in users
    app.UseAntiforgery();

    app.UseMvcWithDefaultRoute();
}

This approach composes with any middleware and doesn't require the use of MVC. Consider this the default for new projects.

Using AutoValidateAntiforgery filter

This strategy will validate all POST/PUT/DELETE/ETC requests and give a 400 response if a valid token is not included.

public void ConfigureServices(IServiceCollection services)
{
    // Require a token on POST/PUT/DELETE/ETC for logged in users
    app.AddMvc(options =>
    {
        options.Filters.Add(new AutoValidateAntiforgeryAttribute());
    });
}

This approach is MVC specific. Use this is you want to protect a site using MVC with no authentication.

imranbaloch commented 8 years ago

Requests without a valid token for a logged in user will be redirected to a logic page.

Shouldn't making it unauthorized/forbidden is better if he is already logged-in?

rynowak commented 8 years ago

Added some more detail here - this actually flows back into the authorization system, which ultimately decides what to do.

Requests without a valid token for a "logged in" user will be treated by the auth system as "not logged in". This might mean a challenge, login screen, or unauthorized response.

Vasim-DigitalNexus commented 8 years ago

Very nice!

ShadowDancer commented 8 years ago

Will there be a way to disable antiforgery system in development environment? Currently it's not straightforward (I had to register my wrapper for defaultAntiforgery). Testing endpoints with rest client is a pain if you have to think about tokens.

Also another option I could think of is to disable cookie with antiforgery (in my application I'm fighting for every byte of cookie and because of that my tokens are rendered on page with each request).

imranbaloch commented 8 years ago

IMHO, its better for the middle-ware to throw exception rather then wiping out existing principal. I think we are mixing authentication system with XSRF(you will see lot of confusion in future) cc @blowdart

blowdart commented 8 years ago

I would be fine with an exception, its what previous versions do. It might also reduce confusion.

imranbaloch commented 8 years ago

Let me give a realistic example. Currently we have big mobile app(which have lot of forms in webview(built using asp.net mvc 5)) with lot of users. Daily I see lot of missing xsrf token exceptions. The reason of these exceptions is disabled cookie policy. Getting these exceptions we at least know what errors our users are getting so that we can help them to fix the issues.

Now let assume that we have the above middle ware. For users that have cookie disabled policy will always redirected to login page during submission. This will be very hard for us to track and users will be confused.

AlexChesser commented 8 years ago

When dealing with Ajax, APIs and, Antiforgery ... are there any best or recommended practices or watch-outs you can think of?

With the rise of front-end-frameworks (angular, react, mithril) and the growing popularity of microservices style architectures, I'm wondering if there is a looming CSRF security nightmare in the future. (and from personal experience, had to spend a bit of time doing some mildly yucky hacks)

WIth the example of an frontend-framework page. These are often loaded as flat & totally non-magical html. Dom elements are then rendered out of a mix of javascript and the json result of API calls.

From that point of view, what would be the best way to get AF-Tokens? Perhaps we should be writing an API endpoint to get an AntiForgeryToken?

In that case we would still be able to use the Action decorators but would have to do a few manual steps around making sure we added the AF to any requests.

rynowak commented 8 years ago

Update 3/30

We're not 100% satisfied with what we built here, so we're not going to make any changes to the default experience for validating tokens in RC2.

We're not going to ship the AntiforgeryMiddleware because we're not confident that it's our long term approach. We're still going to be looking at a solution that works with middleware, with the authentication system and can protect more of your application than just MVC.

In MVC - the @Html.BeginForm and <form> taghelper will still generate an antiforgery token by default. The [AutoValidateAntiforgeryToken] filter will be shipped in RC2 and will be available for you to use, but will not be configured by default.

304NotModified commented 8 years ago

Sounds like a wise decision.

rynowak commented 8 years ago

Thanks for the feedback, I've haven't stopped by to address comments here because we were still discussing whether or not we wanted to stick to the plan that was announced. Security is hard, and auth is hard.

Shouldn't making it unauthorized/forbidden is better if he is already logged-in?

Yes, we got this feedback loud and clear. We want this to be available as an option - not going to speculate right now on whether or not it would be the default.

Will there be a way to disable antiforgery system in development environment? Currently it's not straightforward (I had to register my wrapper for defaultAntiforgery). Testing endpoints with rest client is a pain if you have to think about tokens.

I'm not sure we'd do anything here other than to say that tying antiforgery together with auth is related to this. If you're using cookies for auth, then antiforgery is required. Antiforgery isn't required if you're using bearer auth. We want to make it possible to write endpoints that support both kinds of authN and do the right thing for antiforgery based on what kind of authN is used for the request.

Cases where you aren't using auth are harder.

rynowak commented 8 years ago

When dealing with Ajax, APIs and, Antiforgery ... are there any best or recommended practices or watch-outs you can think of?

In general the choice here is whether your ajax calls are using a persistent authN mechanism like cookies, or a bearer token. Using bearer auth requires some kind of token server in the app (or a 3rd party token server), but has fewer gotchas - it's inherently not vulnerable to CSRF.

If you are using cookie auth for an API backend, it's part of a first-party app, meaning that you own both the frontend and backend. So, write your backend in a way that meets your requirements for your app.

We've added support for accepting the CSRF token via header in RC2 to be more ajax friendly. Generally ajax libraries (including jQuery and Angular) have a convention around how to find it.

Example here with Angular: https://github.com/aspnet/Antiforgery/blob/dev/samples/AntiforgerySample/Startup.cs#L36

From that point of view, what would be the best way to get AF-Tokens? Perhaps we should be writing an API endpoint to get an AntiForgeryToken?

This isn't one-size-fits-all advice, but my suggestion is to send back the request-token when the user logs in. That could look like setting a cookie that your client knows about (angular), or sending it back in a JSON blob that might in the user's display name, and other state you need to know on the client when the user has logged in.

I'm not aware of any problem with exposing an endpoint that returns the token. That's basically equivalent to what a normal CSRF flow is like with @Html.BeginForm, and would require XSS to exploit.

/cc @blowdart because he's the expert

blowdart commented 8 years ago

The only thing I have to add is don't write an API endpoint to get an anti-forgery token, write an API auth mechanism that doesn't need one. Separate your first party and third party API endpoints should you feel the need to use cookie auth because your first party website is calling APIs.

anil-kk commented 8 years ago

Hello @rynowak
Please check this https://github.com/aspnet/Antiforgery/issues/94

Eilon commented 7 years ago

We are closing this issue because no further action is planned for this issue. If you still have any issues or questions, please log a new issue with any additional details that you have.