Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd CSRF protection #14
Comments
SergioBenitez
added this to the 0.2.0 milestone
Sep 25, 2016
SergioBenitez
added
the
enhancement
label
Sep 25, 2016
SergioBenitez
changed the title
Add CSRF prevention mechanism
Add CSRF protection
Sep 25, 2016
SergioBenitez
modified the milestones:
0.3.0,
0.2.0
Sep 29, 2016
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Jan 8, 2017
•
|
Tying this specifically to forms is a bad idea because it makes supporting Single Page Applications (SPAs) written in JavaScript more difficult and can break support for AJAX requests that use json/xml APIs. It's common for those applications to send the token in an HTTP request header instead of in the form body, especially if the body isn't a standard form encoded value (eg. JSON, XML), because it's easier to simply add a header before a request is sent than have to rewrite the DOM each time a request is made. A common way to handle this is to provide two code paths for the check determined by the presence and value of the X-Requested-With header. jQuery specifically, and other AJAX libraries, set the X-Requested-With header before sending a request and set the value to "XMLHttpRequest". In the case that the header isn't set or it's not that value defaulting to looking in the form payload for the specified value is the correct place to find the token. Otherwise, if the header is set and it denotes an xhr you look in the header for the token. It's also worth noting that a reasonable implementation should depend on sessions existing for storage of the token. I'm happy to help work on this if it's not already being done. |
This comment has been minimized.
This comment has been minimized.
|
I'd like to also have CSRF protection by default on when accessing the site with any mutating HTTP method, like POST, PUT or DELETE. At least check the origin + referer by default in these cases, and make it easy to additionally annotate routes that need checking, plus make it easy to have a HTTP header or cookie based check too. |
This comment has been minimized.
This comment has been minimized.
scull7
commented
May 3, 2017
|
Is there anyone working on this? If not, I wouldn't mind having a go at it. I believe that checking for a header token and then falling back to the form body would cover most cases (80/20 rule). |
This comment has been minimized.
This comment has been minimized.
|
With a rather large sigh of grief, I'm pushing this to Implementing this in a simple, efficient, and straightforward manner has been a challenging endeavor. While it's simple to validate the incoming request against CSRF, it's not simple to emit a response with the appropriate information for later validation. In particular, we'd like to make it easy and efficient to insert a CSRF token into a web form in a template. To complete the defense mechanism, we need to insert the same token into a cookie. This presents a challenge: how do we know that a token has been inserted into a template so that we can then use the same token as a cookie in the response? I have two candidate solutions, each with a somewhat large drawback.
I believe that choice #2 above is a better approach. I'm against adding any kind of global state, though I'll note that this is precisely what other frameworks use to implement this kind of automatic CSRF protection. Any thoughts, suggestions, or ideas on this matter would be greatly appreciated. This is an important feature to have, and I'm excited to really nail down a good solution here. |
SergioBenitez
modified the milestones:
0.4.0,
0.3.0
Jun 2, 2017
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Jun 3, 2017
|
I'm not following why global state is needed for implementing the token system or why the design of 1 is being considered. Validation and generation can always happen at request time before a response with the template ever occurs. Typically the way most frameworks handle this is when a request comes in the framework determines if the request requires a token and if it needs to be validated. If not, it's simply a nop and the the request/response cycle goes through as expected. In the case where a token needs to be validated one of two events can happen:
You should never need to know a prior token's value and that information should not need to be kept around or generated at template generation time. Furthermore you also shouldn't need to expose a global, the template's scope just needs to include access to the session where the token can be taken out of it. |
This comment has been minimized.
This comment has been minimized.
|
As I said in my previous comment:
Validation is easy, as you've determined. As is generating a new token. The hard part (for us) is inserting that token in two places: a template and a cookie. We want to use a new token for each response requiring one, and we don't want to generate a new token unless we're going to eventually use it. Thus, generating a new token each time a request is received is a non-starter. You say:
But that's exactly part of the issue; how do you do that? You've already assumed that there's some global session that we can simply refer to: there isn't. We also can't simply inject information into a template's scope: the user specifies the scope. It is conceivable that we can extend existing template engines with a second, framework provided scope. This would be equivalent to my second proposal except with information flowing the inverse direction. Let me try to be as clear as possible. In a template, a user should be able to write something like: <form ..>
<!-- in practice, a form helper will generate the form tag AND the token field -->
{% csrf_token %}
</form>Rocket should generate the CSRF token hidden field from the
In my proposal, |
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Jun 3, 2017
|
I understand the issue now. One suggestion I have, and this may not be the popular opinion or desired behavior, is to not try and push the token injection into the templates as part of rocket. I think unless rocket is dictating the template/rendering engine. The reason I say this is a couple reasons: First, There's going to be a lot of variation and support needed as well as buy in from all the developers working on the engines. Second, The updating of the tokens itself is going to be very app specific and going to be difficult to impossible to handle without providing a frontend js component like Rails does with it's UJS. If the application is using xhr the template may not be re-rendered and rotating the token on the server side (especially if it wasn't validated) will immediately break the page. In my day job we had to solve this problem for apps that use our security plugin and what we did was append some JavaScript to the end of the any responses that have a handful of characteristics (content-type, etc.). This puts a small wrapper around the xhr code as well as adds an event listener into the dom and delegates to a handful of elements (submit, a, button) to add tokens into xhr requests from a cookie do some clever dom manipulation to drop the token into forms before the request is submitted. This let's us avoid parsing the response and manipulating it to try and put the tokens into the forms. The parsing wont be an issue for Rocket because the location is going to explicitly be set for where insertion is needed but this would solve the issue of tokens updating and the dom not being rewritten. This solution is sort of out of the scope of Rocket as a rust based web framework but it's a solution that we've found solves some of these problems well. |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Aug 31, 2017
|
@SergioBenitez May I ask why? Generating a token is easy. It should be as simple as grabbing a few bytes from an optimized implementation of ChaCha20 or (if AES-NI is available) AES256-CTR. Much, much less effort than the upstream proxy server spent decrypting the incoming HTTPS request. Regarding sessions: Rocket already supports authenticated encryption of cookies. That means that we can store the token client side without any security loss. Client side code can still read the token, but can’t tamper with it without being detected (resulting in a Finally, just checking |
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Aug 31, 2017
|
@DemiMarie so I'm sure that Sergio will have more to add to this but it's not so much the generation that's difficult but the submitting the tokens with requests. The problem becomes getting the token into the DOM for form based requests and into XHR requests. If the goal is to provide server side templating with support for csrf tokens (which it appears this is a goal) the hard part is getting the token into the template and updating the template. Rails solved this by providing some helpers with global state on the server side rendering end and some additional JS that was part of their UJS package. Global state is undesired in this case which complicates the problem significantly. One solution that might be worth looking at is providing two separate pieces of middleware. One to handle generation/validation of tokens as well as adding them into responses for JS SPA frameworks, such as angular, to use that already have built in support for this. Then provide a second middleware or component that adds support for the helpers on the server side rendering. If you're looking for more ideas I spoke at DEF CON this year about a solution I outlined above that included some JS that is injected to provide support client side. It seemed that there wasn't interest in a solution like this but just to offer more options. https://www.youtube.com/watch?v=foFf4gMbL2w |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Aug 31, 2017
|
@jrozner What about having some sort of per-request state? I really really really don’t want the solution to be dependent on JS. I personally use NoScript, for instance, and believe that a simple form should not require JS to submit. There are also accessibility issues, if I recall correctly, though I don’t remember what they are. |
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Aug 31, 2017
•
|
@DemiMarie The issue isn't so much about the state at the handler point but providing the state to the templating engine in a way that the templating engine can actually use it. The state in sessions is probably fine at the handler level but getting that into a template is more complicated. Part of the problem is attempting to solve this for all cases and Rocket not being opinionated allowing you to bring basically any templating with you or bring none (eg. no server side rendering and returning JSON). I realistically don't see a possible solution that isn't split into multiple parts because a non-js dependent solution that allows for server side rendering but also for SPAs (or at least requests that don't re-render) is incompatible and solve very different problems. One problem is generation, validation, and storage of tokens while the other is insertion of tokens into requests and updating tokens after responses. Supporting arbitrary templating engines isn't really a csrf problem but is required to be solved for server side rendering in Rocket and providing necessary support. Either adapters likely will need to be provided for specific templating engines to provide a common interface for this or maybe an alternative solutions. What I'm proposing here is build the functionality for dealing with generation, validation, and providing tokens in responses so this becomes available now for a large majority of users and applications that don't have the specific requirements you do. At this point, whether you like it or not, NoScript is not viable for most parts of the web and in most cases JS based SPAs have won and their frameworks handle the token insertion for you. |
This comment has been minimized.
This comment has been minimized.
ssokolow
commented
Aug 31, 2017
Unless you've got some objective research here, I'm going to say we're both suffering from selection bias. I certainly don't see them as having won, nor do I see NoScript as being non-viable. |
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Aug 31, 2017
|
@ssokolow I'll cede to that and accept that you might be right here. Given trends in web development I doubt it but it's very possible. However, the earlier point about separate problems I think still holds and is relevant. |
This comment has been minimized.
This comment has been minimized.
seanlinsley
commented
Nov 25, 2017
It appears that checking Origin & Referer are sufficient, except when:
|
This comment has been minimized.
This comment has been minimized.
ssokolow
commented
Nov 25, 2017
•
Here are another two reasons which CSRF protection via the Referer header is inadequate:
|
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Nov 25, 2017
|
@seanlinsley suggesting that it's reasonable to simply have an application not work for users behind a proxy is a pretty concerning stance. Especially if the application can't explain why it's not working for the user. We shouldn't make users choose between security and functionality when there are solutions available that don't require that. |
This comment has been minimized.
This comment has been minimized.
seanlinsley
commented
Nov 26, 2017
|
@ssokolow could you link to such browser extensions? Is there data to suggest that they block the referer header indiscriminately on any website, even when navigating from page to page on the same domain? (as opposed to stripping the header once it becomes a cross-domain request) I'm not sure I understand the argument in point 2. Could you elaborate? @jrozner AFAICT proxies in general don't remove the header unless specifically configured to. This is entirely explainable to end users. For example:
|
This comment has been minimized.
This comment has been minimized.
ssokolow
commented
Nov 26, 2017
•
|
Firefox has it built in through these
...though I prefer to use an extension like RefControl (legacy) or Smart Referer (WebExt) which implements a whitelist similar to NoScript. (RefControl, which I've used for years and which I'll continue to use until I get off Firefox 52ESR, allows you to choose between Block (no referrer), Forge, and Allow, both for the default policy and for per-domain rules... including ones which only apply to 3rd-party requests.) For Chrome, you either launch it with |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Nov 26, 2017
|
That still means that some users behind corporate proxies cannot use the
app. This is bad!
…On Nov 25, 2017 8:06 PM, "Sean Linsley" ***@***.***> wrote:
@ssokolow <https://github.com/ssokolow> could you link to such browser
extensions? Is there data to suggest that they block the referer header
indiscriminately on any website, even when navigating from page to page on
the same domain? (as opposed to stripping the header once it becomes a
cross-domain request)
I'm not sure I understand the argument in point 2. Could you elaborate?
@jrozner <https://github.com/jrozner> AFAICT proxies in general don't
remove the header unless specifically configured to.
This is entirely explainable to end users. For example:
Unable to verify your request. Please try again after turning off privacy
extensions in your browser, or moving to a different internet connection.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#14 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGGWBzBez85iSeV0cCq88lBLL-AcQHrQks5s6LmqgaJpZM4KF2dV>
.
|
This comment has been minimized.
This comment has been minimized.
ssokolow
commented
Nov 26, 2017
•
|
...as for point 2, my argument is that I don't think it's robust enough to rely exclusively on headers for CSRF protection and the OWASP document seems to agree. The whole point of the token-based approach is twofold:
Heck, I watched a talk where one of the takeaways was "Don't assume your design's custom fonts will load. There are still corporate proxies that don't have headers like |
This comment has been minimized.
This comment has been minimized.
seanlinsley
commented
Nov 26, 2017
|
FWIW the above-linked Smart Referer browser extension only removes the header when navigating cross-domain. From my point of view, if you have corporate customers you can either afford the budget to write a ton of custom code, or you can get them to whitelist your site so the headers aren't stripped. Given @SergioBenitez's desire not to add global state, and the complexity involved in building a common API for multiple possible template engines, this seems like a reasonable solution that will work for almost all users. The perfect is the enemy of the good, etc. |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Nov 26, 2017
|
I think that global (well, request-local) state is not a big deal.
…On Nov 26, 2017 2:09 PM, "Sean Linsley" ***@***.***> wrote:
FWIW the above-linked Smart Referer browser extension only removes the
header when navigating cross-domain.
From my point of view, if you have corporate customers you can either
afford the budget to write a ton of custom code, or you can get them to
whitelist your site so the headers aren't stripped.
Given @SergioBenitez <https://github.com/sergiobenitez>'s desire not to
add global state, and the complexity involved in building a common API for
multiple possible template engines, this seems like a reasonable solution
that will work for almost all users. The perfect is the enemy of the good,
etc.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#14 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGGWB5HbNAnGBcvnsl2PZa7UMxDGVFCOks5s6bdogaJpZM4KF2dV>
.
|
This comment has been minimized.
This comment has been minimized.
avanov
commented
Nov 26, 2017
•
This is not how B2B works, though, and you rarely have a luxury of an extra budget or access to established corporate security policies. And the customers in this setting really care about security and ability to solve as many domain problems (and their specific cases) with the tool of choice as possible, and they rarely care about specifics the tool is implemented with, i.e. they don't care if CSRF is managed by a global state or some other magical approach that doesn't require the state. Hence, the criteria list is:
I agree that the perfect is the enemy of the good, yet I think this GH issue is the perfect counter-example of this motto - it's been more than a year since the initial proposition, and there's no production-ready working solution to CSRF handling in Rocket to the moment, even a mediocre one. |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Nov 27, 2017
|
BINGO!!!!!!!!
…On Nov 26, 2017 2:41 PM, "Maxim Avanov" ***@***.***> wrote:
From my point of view, if you have corporate customers you can either
afford the budget to write a ton of custom code, or you can get them to
whitelist your site so the headers aren't stripped.
This is not how B2B works, though, and you rarely have a luxury of an
extra budget or access to established corporate security policies. And the
customers in this setting really care about security and ability to solve
as many domain problems (and their specific cases) with the tool of choice
as possible, and they rarely care about specifics the tool is implemented
with, i.e. they don't care if CSRF is managed by a global state or some
other magical approach that doesn't require the state. Hence, the criteria
list is:
- does it cover our use cases?
- is it reliable?
- is it secure?
- how much it cost us on our use cases?
I agree that the perfect is the enemy of the good, yet I think this GH
issue is the perfect counter-example of this motto - it's been more than a
year since the initial proposition, and there's no production-ready working
solution to CSRF handling in the Rocket to the moment, even a mediocre one.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#14 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AGGWB9CwIwSHjlg3rBWm1yYirisFyvFtks5s6b7fgaJpZM4KF2dV>
.
|
This comment has been minimized.
This comment has been minimized.
|
This topic has garnered a very healthy amount of discussion, and for good reason, too! CSRF is an important attack vector to protect against, not least because it is so commonly seen in the wild. I want to reiterate that I'm committed to ensuring that Rocket protects against CSRF in version 0.4. For the reasons I discussed in my previous comments (#14 (comment), #14 (comment)), there are unique challenges to implementing automatic CSRF protection in Rocket. The root cause of many of these challenges is the desire to avoid global state. This desire isn't simply an argument for elegance; any notion of global state jeopardizes the ability to run multiple instances of one Rocket application in the same process. This exact scenario occurs when running tests, for instance. I've been working on a solution that resolves these issues. The solution closely aligns with option 2 in my original comment ("Add support for rendering state to existing template engines.") but takes a slightly different approach than what was originally proposed. While I'm not ready to announce the exact mechanisms yet, the solution will resolve these issues. Additionally, this approach will also protect Rocket applications against XSS attacks. In fact, if all goes well, Rocket will provide a compile-time guarantee against CSRF and XSS attacks, something I'm extremely excited about! I'm looking forward to announcing more about these efforts as progress continues. Until then, I appreciate all of the commentary. Your support, in any form, is invaluable. |
This comment has been minimized.
This comment has been minimized.
DemiMarie
commented
Dec 5, 2017
That is FANTASTIC!!!!!! Does that also include a guarantee that the application doesn’t (say) try to interpolate user input into a |
This comment has been minimized.
This comment has been minimized.
jrozner
commented
Dec 5, 2017
|
Will the solution you're working on support applications that don't use server side rendering? Most frontend frameworks that utilize xhr depend on the token being inserted into an HTTP response headers instead of in a rendered template? |
This comment has been minimized.
This comment has been minimized.
japrogramer
commented
Dec 10, 2017
|
In django we check the header and a http only header, I think that will take care of that concern. |
This comment has been minimized.
This comment has been minimized.
novacrazy
commented
Feb 12, 2018
|
How is this coming along? |
This comment has been minimized.
This comment has been minimized.
seanlinsley
commented
Feb 12, 2018
|
I'd be happy to help with this @SergioBenitez |
This comment has been minimized.
This comment has been minimized.
mb1986
commented
Mar 6, 2018
•
|
Here is my current approach to CSRF protection: use rocket::{Rocket, Request, Response, Data};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::{StatusClass, Cookie, Method};
use rocket::response::Failure;
use rocket::http::Status;
use uuid::Uuid;
const XSRF_TOKEN: &str = "XSRF-TOKEN";
const X_XSRF_TOKEN: &str = "X-XSRF-TOKEN";
#[get("/403")]
fn error_403() -> Failure {
Failure(Status::Forbidden)
}
pub struct CsrfProtection;
impl Fairing for CsrfProtection {
fn info(&self) -> Info {
Info {
name: "CSRF Protection",
kind: Kind::Request | Kind::Response | Kind::Attach
}
}
fn on_request(&self, request: &mut Request, _: &Data) {
match request.method() {
Method::Post | Method::Put | Method::Patch | Method::Delete => {
if !request.cookies()
.get(XSRF_TOKEN)
.map(Cookie::value)
.and_then(|cookie_token|
request.headers()
.get_one(X_XSRF_TOKEN)
.map(|header_token| cookie_token == header_token)
).unwrap_or(false) {
let new_uri = format!("/403#{}", &request.uri());
request.set_uri(new_uri);
request.set_method(Method::Get);
}
},
_ => (),
}
}
fn on_response(&self, request: &Request, response: &mut Response) {
if response.status().class() == StatusClass::Success {
response.set_header(
&Cookie::build(XSRF_TOKEN, Uuid::new_v4().hyphenated().to_string())
.path("/")
.finish()
);
}
}
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
Ok(rocket.mount("/", routes![error_403]))
}
}
pub fn path<'r>(req: &'r Request) -> &'r str {
req.uri().fragment()
.unwrap_or(req.uri().path())
}The use of the above Fairing: rocket::ignite()
.attach(security::csrf::CsrfProtection)
// ...
.launch();And path resolution in catcher: #[error(403)]
fn forbidden(req: &Request) -> Json {
Json(json!({
"status": 403,
"message": "Forbidden",
"path": path(req)
}))
} |
This comment has been minimized.
This comment has been minimized.
dgriffen
commented
Aug 2, 2018
|
Its a quite recent development but the SameSite attribute on cookies may also be a way to prevent these sorts of attacks. Although it probably woudn't be a good default for Rocket since its such a new standard and as such isn't widely supported. |
This comment has been minimized.
This comment has been minimized.
|
@dgriffen Rocket has been using |
This comment has been minimized.
This comment has been minimized.
dgriffen
commented
Aug 3, 2018
|
@SergioBenitez can you elaborate on the cases where it isn't robust enough? Is it just because some browsers may not properly implement it (similar to how some browsers may not send |
This comment has been minimized.
This comment has been minimized.
|
@dgriffen The |
This comment has been minimized.
This comment has been minimized.
ssokolow
commented
Aug 3, 2018
|
Also, a CSRF token can be used to ensure users are following a certain request flow, while something like SameSite would allow an attacker to attack any part of your site from any other part of your site if they find an XSS vulnerability. |
This comment has been minimized.
This comment has been minimized.
|
I'm pushing this to |
SergioBenitez commentedSep 25, 2016
CSRF is a thing. Protect against it automatically in forms.