jhipster / generator-jhipster

JHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures.
https://www.jhipster.tech
Apache License 2.0
21.56k stars 4.02k forks source link

Enable CSRF #363

Closed HimalayFei closed 9 years ago

HimalayFei commented 10 years ago

First of all thank you so much for doing the hard and smart work.

After enabling CSRF in Spring Security 3.2, the system is not behaving properly, we even tried few workarounds to properly read the csrf tokens. I was wondering if you guys can also include this feature in jhipster generated code, or give some better documentation around it. There is no standard documentation available online.

Thank you!

jdubois commented 10 years ago

Thanks for the nice feedback! As far as I'm concerned I'm not using CSRF (I had trouble configuring it, but that was a long time ago). I'm notifying specifically:

jmirc commented 10 years ago

I disabled CSRF to support the iFrame used by Swagger.

Could you describe your current issues?

HimalayFei commented 10 years ago

On enabling CSRF i.e removing csrf().disable() from Security Configuration, we got the error message "Expected CSRF token not found. Has your session expired?" .

My Workaround

After going over the response in Chrome Dev Tools, I did not find any trace of CSRF token, so I created a custom CsrfTokenGeneratorFilter, which adds CSRF token to response headers.

public final class CsrfTokenGeneratorFilter extends OncePerRequestFilter {  
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CsrfToken token = (CsrfToken) request.getAttribute("_csrf");

        // Spring Security will allow the Token to be included in this header name
        response.setHeader("X-CSRF-HEADER", token.getHeaderName());

        // Spring Security will allow the token to be included in this parameter name
        response.setHeader("X-CSRF-PARAM", token.getParameterName());

        // this is the value of the token to be included as either a header or an HTTP parameter
        response.setHeader("X-CSRF-TOKEN", token.getToken());

        filterChain.doFilter(request, response);
    }
}

I then added the above filter to Security Config

    @Override
    protected void configure(HttpSecurity http) throws Exception {      
        http
            .addFilterAfter(new CsrfTokenGeneratorFilter(), CsrfFilter.class)
...
...

Now that I was able to see the CSRF token in the response header, I needed a way to read that in landing page of my application and I found this angular utility. https://www.npmjs.org/package/spring-security-csrf-token-interceptor

I just had to change the var xhr.open('head', '/', false); To var xhr.open('head', '/myapp', false); (as my landing page is at http://localhost:8080/myapp)

(function () {
    'use strict';
angular.module('spring-security-csrf-token-interceptor', [])
    .config(function($httpProvider) {
        var defaultCsrfTokenHeader = 'X-CSRF-TOKEN';
        var csrfTokenHeaderName = 'X-CSRF-HEADER';
        var xhr = new XMLHttpRequest();
        xhr.open('head', '/myapp', false);
        xhr.send();
        var csrfTokenHeader = xhr.getResponseHeader(csrfTokenHeaderName);
        csrfTokenHeader = csrfTokenHeader ? csrfTokenHeader : defaultCsrfTokenHeader;
        var csrfToken = xhr.getResponseHeader(csrfTokenHeader);
        if (csrfToken) {
            $httpProvider.interceptors.push(function($q) {
                return {
                    request: function(config) {
                        config.headers[csrfTokenHeader] = csrfToken;
                        return config || $q.when(config);
                    }
                };
            });
        }
    });
})();

This above approach allows me to login (authenticate), however when I make a POST request AFTER I login, it fails.

I noticed in Chrome Dev Tools that the token is being generated twice, once before login and once after login, but the above angular utility intercepts and reads the CSRF token only once before login and just uses that there after. So any PUT/POST requests I get "Invalid CSRF Token '9808d062-57c8-42c5-9195-54a22315855a' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'."

The CSRF token 9808d062-57c8-42c5-9195-54a22315855a is the one which was generated before I logged in. After I logged in I see server sent me a new CSRF token 836d242c-91b4-41a9-9978-d88e1bc830ee, but the angular utility continued to used the first token.

After going thru Spring docs http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#csrf-include-csrf-token . I also tried including the following in my index.html but nothing changed there.

    <meta name="_csrf" content="${_csrf.token}"/>
    <!-- default header name is X-CSRF-TOKEN -->
    <meta name="_csrf_header" content="${_csrf.headerName}"/>

All this tells me I am doing something terribly wrong and that there could be a better approach to resolve this CSRF issue.

Please let me know if you need some extra info.

rwinch commented 10 years ago

The current implementation will deliberately update the CSRF token. This is to protect against types of session fixation attacks. You will need to be sure to update the token you submit as the CSRF is updated (i.e. after you submit a request be sure to update your model with the resulting headers).

HimalayFei commented 10 years ago

Thanks for clarifying Rob, I am noticing that CSRF token gets generated TWICE (before and after authentication), is that correct?

rwinch commented 10 years ago

@HimalayFei Yes that is correct. However, you will likely want to update the token on each response as there is the possibility of new strategies being available in the future. Naturally Spring Security will remain quite passive, but it is best to program against the API (i.e. update on each response) rather than the implementation (i.e. update first page load and after authenticating).

HimalayFei commented 10 years ago

As far as AngularJS is concerned, it is designed expects CSRF token in a "session cookie" (by default, XSRF-TOKEN) and sends this token to server in Request Headers. So I have two options now

  1. Customize Spring to send CSRF token in a "session cookie" which AngularJS is designed to read.
  2. Send back CSRF token from Spring in Http Response Headers and customize AngularJS to read the response headers.

Either way, having some examples/documentation around it would help immensely. Thank you.

rwinch commented 10 years ago

@HimalayFei There is a generic JIRA, SEC-2460, for documenting CSRF w/ static pages. It is quite challenging to provide exact steps for integration with every framework. I would LOVE to see a PR with a sample application and AngularJS submitted to Spring Security. The sample should reuse most of the logic from the message sample application that is used throughout the rest of the sample applications. If you have time writing a guide with AngularJS would also be appreciated.

HimalayFei commented 10 years ago

Sure Rob, I am free this weekend and will work on this to get a working prototype with my research documentation out.

rwinch commented 10 years ago

@HimalayFei Awesome! Remember it doesn't have to be perfect, we can iterate on it if necessary. Even just a basic sample application will certainly help users better than what is there now! Thanks again!

jdubois commented 10 years ago

@HimalayFei any updates on your prototype? Will you be able to add this to JHipster also?

HimalayFei commented 10 years ago

@Julien I apologize for the delay.

I generated a skeleton using jHipster (https://github.com/HimalayFei/csrf-jhipster/commit/c30fcacbaab6eae33ade60385e2e5a28a1234427) and enabled CSRF, here is the changeset https://github.com/HimalayFei/csrf-jhipster/commit/c30fcacbaab6eae33ade60385e2e5a28a1234427 . I however think that this solution should be optimized/corrected as it is more like a hack around. I did not get a chance to document my reasoning behind this approach, but I plan to do it this weekend without any further delay. Please let me know if you think its worthy enough to be added to jHipster family. Thanks for your patience.

ctamisier commented 10 years ago

Hello, I am not sure but can the use of @EnableWebMvcSecurity (vs @EnableWebSecurity) help for Csrf integration (declare a CsrfRequestDataValueProcessor Bean) ?

rwinch commented 10 years ago

Yes. Using @ EnableWebMvcSecurity will provide some integrations that will help (like adding a CsrfRequestDataValueProcessor). The filter would still be necessary at this point since the CsrfRequestDataValueProcessor would not impact a single page application.

jeffsheets commented 10 years ago

I'm not an OAuth expert, but another thing to keep in mind, if the user selects to generate their app with OAuth token logins then most of this discussion is different. They essentially get much of the CSRF protection for free by usage of the Bearer tokens. Would just have to verify that jHipster is using the 'state' URL param correctly: https://spring.io/blog/2011/11/30/cross-site-request-forgery-and-oauth2 http://www.twobotechnologies.com/blog/2014/02/importance-of-state-in-oauth2.html

HimalayFei commented 10 years ago

I am about to expose a Spring MVC web service using SAML and haven't thought about CSRF token exchange yet. I hope the same applies to SAML as well. Thanks for sharing the links.

jeffsheets commented 10 years ago

@HimalayFei I improved your sample app to intercept the token off of every response to use on the next request https://github.com/jeffsheets/csrf-jhipster/commit/4ca0c26853d0c1dc8a1aea1c7ef2e276f1f4f54c and also created a pull request for you.

After doing this though, it feels to me like the better solution might be to have a filter create a session cookie and then use the default token handling from AngularJS (your "Option 1" from above). The session cookie approach could be easily reused by other Spring Security apps that are using AngularJS without needing to modify both server and client code... I'll give it a try this week if I have time

jeffsheets commented 10 years ago

@HimalayFei created a second pull request https://github.com/HimalayFei/csrf-jhipster/pull/2 that uses the Session Cookie approach. But I got snagged by AngularJS $http holding onto the XSRF token value, but spring security changes the XSRF value after logging in.

jdubois commented 10 years ago

@jeffsheets @HimalayFei any updates on this issue? I'm sorry but I don't know CSRF well enough to provide a solution myself.

HimalayFei commented 10 years ago

@jdubois There was some improvement proposed by @jeffsheets to my original solution and I am not totally convinced by it as it fetches csrf token from the server for every GET request (I had limited it to PUT and POST requests) and increasing the http traffic. If @jeffsheets or @rwinch strongly think we need to fetch CSRF token for every request, I will accept @jeffsheets pull request and send a pull request to JHipster.

HimalayFei commented 10 years ago

@jdubois @jeffsheets Just FYI, I have been able to use the CSRF solution I proposed in my project successfully and it passed penetration testing with IBM App Scan.

jdubois commented 10 years ago

@HimalayFei thanks for the feedback. As performance is always an issue, when you do your PR, can you also explain what will cause problems? Is this just sending a bigger cookie?

HimalayFei commented 10 years ago

@jdubois Its actually about sending an extra request to server for every GET call, and with Angular there are times when I have seen multiple calls to fetch CSRF token from the server.

Sorry, but did you mean PR == Pull Request?, I will attach the screenshot of Network traffic from Chrome. I will however wait for @jeffsheets for a day before sending the pull request.

jdubois commented 10 years ago

@HimalayFei in that case that means a huge performance hit. We can't have this, at least not as the default option. Is this how CSRF is supposed to work normally? Yes by "PR" I meant Pull Request.

HimalayFei commented 10 years ago

I don't know the internals of Spring session to say this is how exactly CSRF should work (may be @rwinch can shed some light), but getting a token for every GET request to me is a performance hit, and so I filtered out all the GET requests and so far it has been working fine for us. Here is a high level sequence diagram of CSRF using JHipster architecture in our project. https://raw.githubusercontent.com/HimalayFei/csrf-jhipster/dfaa04da5cf4bbd3d6f07a46d3b3ea5376d5d3c7/CSRFSequence.jpg

jeffsheets commented 10 years ago

Perhaps I'm missing something, but I don't believe the code is making an 'extra' AJAX call to fetch the tokens. It is just reading the token off the response object whenever a response is received. But I could definitely be missing something.

I was just trying to provide an answer to the approach that @rwinch had proposed (after you submit a request be sure to update your model with the resulting headers). This was in the first pull request.

The second pull request was in regards to the 'session cookie' approach. But I never got that one finalized.

But saying that, I'm definitely not a CSRF expert, just trying to help. So if the solution you have works and passes the audit then I vote we just go with it.

HimalayFei commented 10 years ago

Thank you @jeffsheets, I will actually do a little more digging and comparison just to make sure. PS - I am not a CSRF expert either :).

rwinch commented 10 years ago

Typically the CSRF token will only change upon the user authenticating and logging out. However, it is best practice to update the token which can be provided on the response headers for every request.

Using Spring Session does not prevent CSRF attacks. Please see the reference for more information on when to use CSRF protection http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#when-to-use-csrf-protection

BhawaniSingh commented 10 years ago

Rob that's session Id that changes on successful login. Csrf token changes on each request. I am currently on my phone. I will create a layout of how Csrf works. U need to put csrf token in every single form that u submitting to server. It's very simple and easy. But I will explain later when I am using my system. I am not a security expert but I am testing my company's software for these kind of security issues. As far as performance is concerned there is no extra load on server <1%

rwinch commented 10 years ago

Csrf token changes on each request.

Some implementations do change per request. However, Spring Security's changes per session. The biggest reasons for this are:

BhawaniSingh commented 10 years ago

Rob in my project "If a user clicks the back button, then the application will not work properly", we kick the user out of the system, that was our requirement.

"You will have race conditions for validating concurrent CSRF tokens (i.e. JavaScript environments)" i know nothing about this, as in our system all the validation is done on the server side. Simply we can't trust client as we have to provide highest level of security.

If you aren't changing the CSRF token on each request. CSRF token can be extracted by the attacker using click-jacking or other means.

I don't know, I may be wrong as I am not the security expert.

rwinch commented 10 years ago

@BhawaniSingh

"If a user clicks the back button, then the application will not work properly", we kick the user out of the system, that was our requirement.

This may be the case for you, but many systems require the back button to work

"You will have race conditions for validating concurrent CSRF tokens (i.e. JavaScript environments)" i know nothing about this, as in our system all the validation is done on the server side.

The validation is done on the server side. However if you have two requests being processed concurrently, then there is a chance that they are processed by the server out of order. This means that the CSRF validation will fail. Consider:

If you aren't changing the CSRF token on each request. CSRF token can be extracted by the attacker using click-jacking or other means.

Clickjacking does not allow the user to extract the token since the same origin policy will protect the token. It does allow an attacker to submit a request with a valid CSRF token in an iframe. However, changing the token each request does not mitigate clickjacking attacks. You need to take separate measures to protect against clickjacking (i.e. sending X-FRAME-OPTIONS=DENY)

NOTE that Spring Security's Java Configuration enables both CSRF and Clickjacking protection by default. We did not enable these by default for XML configuration in order to remain passive. However, in 4.0 we will enable them by default for XML configuration too.

BhawaniSingh commented 10 years ago

@rwinch Thanks for your explanation. Now I understand everything well.

dancancro commented 10 years ago

Does anyone know what they mean by this entry of the AngularJS site FAQ?

Do I need to worry about security holes in AngularJS? Like any other technology, AngularJS is not impervious to attack. Angular does, however, provide built-in protection from basic security holes including cross-site scripting and HTML injection attacks. AngularJS does round-trip escaping on all strings for you and even offers XSRF protection for server-side communication.

AngularJS was designed to be compatible with other security measures like Content Security Policy (CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the possible attack vectors and we highly recommended their use.

I'm trying to keep track of which benefits Angular gives you for free and which ones each of the generators do. From this discussion, it sounds like by itself Angular doesn't really provide XSRF protection at all.

HimalayFei commented 10 years ago

@jeffsheets From the above discussion, it sounds like we should NOT limit CSRF Token request to PUT and POST only, and so I have accepted your PULL request in my code. However, I found a bug in both versions of the code i.e, when we request for Logout to Spring, the Session Cookies are deleted just in UI but the actual http request to Spring Logout fails (which I notice in Chrome Inspection --> Network)

Request URL:http://localhost:9001/jhipster/app/logout Request Method:GET Status Code:404 Not Found

Furthermore, my debugger never makes its way to AjaxLogoutSuccessHandler defined in Spring Security configuration when CSRF is enabled. Could you please verify this in your end. Thank you.

jeffsheets commented 10 years ago

Good catch @HimalayFei . When turning on CSRF protection the Spring LogoutConfigurer forces the logout to use POST instead of GET. http://docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/reference/html/csrf.html

I'll see if I can get it to POST the logout

jeffsheets commented 10 years ago

Submitted https://github.com/HimalayFei/csrf-jhipster/pull/3 to change logout to a POST.

But there's still a bug. The csrf token is not correct when trying to logout. I'm not sure why. I noticed that the .html templates have a (possibly cached) different csrf token in the response, and when clicking logout it quickly loads a couple of templates before calling /app/logout.

I'll let you take a look and see what you think

HimalayFei commented 10 years ago

I now have a solution working now, but I do need one clarification from @rwinch.

Spring doc says "When you use CSRF protection? Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users. If you are only creating a service that is used by non-browser clients, you will likely want to disable CSRF protection."

What if my service is going to be used by both "browser" and "non-browser" clients such as third party external services, does spring security provide a way to disable csrf exclusively for certain type of clients?

rwinch commented 10 years ago

If you are supporting browser and non-browser applications, then the following applies since the request can be processed by a browser:

Our recommendation is to use CSRF protection for any request that could be processed by a browser by normal users

and the following doesn't (it says only non-browser clients)

If you are only creating a service that is used by non-browser clients, you will likely want to disable CSRF protection.

So the recommendation is to enable CSRF protection

HimalayFei commented 10 years ago

Yes Rob, I have enabled CSRF in our app, but now when an external service is trying to call our service it fails because unavailability of csrf token. What I am not sure about is, how to do headless testing when CSRF is enabled.

For example I always used POSTMAN and CURL to test our REST service, now that CSRF is enable and a token is required, I can't test those calls.

jdubois commented 10 years ago

Wouldn't it be easier to just use the CSRF protection provided by AngularJS? See http://kailuowang.blogspot.fr/2013/08/csrf-protection-in-playangularjs.html

Please note that the above solution (with Play!) is ridiculously simple for what we want to do, as they have a "magical" hash function. As our code is Open Source, we couldn't have this function. Anyway, anybody that would be able to reverse engineer it would be able to do CSRF.

jeffsheets commented 10 years ago

This pull request was an initial attempt down that session-cookie route: https://github.com/HimalayFei/csrf-jhipster/pull/2 It had a couple of issues, but if someone has time I'm sure it could be worked around.

@HimalayFei mentioned having a working solution. Perhaps that is ready to go?

jdubois commented 10 years ago

Yes, I've just tried it and it's not as easy as it seems...

jdubois commented 9 years ago

OK, this should be fixed anytime soon by https://github.com/jhipster/generator-jhipster/pull/811 @HimalayFei @jeffsheets @rwinch can you have a look at the PR?

HimalayFei commented 9 years ago

Looks good to me.This is what I did in my project and it has been working fine.

rwinch commented 9 years ago

Generally this approach appears like it will work fine.

However, keep in mind that supporting multiple HTTP sessions in a single browser instance is available now via Spring Session. Think of this as how Google allows you to add multiple accounts. This means there may be a tab that the CSRF token "a" is valid for and another tab that the token "b" is valid for. Using a cookie to provide the CSRF token in this manner may cause complications for multiple sessions in the future.

One way to deal with this would be to store the CSRF token in JS (which would be specific to the page) rather than a cookie (which is available to all tabs).

PS Sorry for the delayed response. I have been on holiday.

tgillieron commented 9 years ago

As the PR only impacts cookie based authentication and jhipster does not support spring-session out of the box, it seems to me that the cookie approach for CSRF is the more pertinent right now.

If the needs to store the token in JS appears it will be possible to do that using a custom http interceptor in angular with minimal refactoring.

rwinch commented 9 years ago

To be clear Spring Session works transparently by adding a Filter (similar to the way Spring Security works). So users will likely expect it to work. In fact it will work with this approach unless they are trying to use multiple sessions in the same browser.

If the needs to store the token in JS appears it will be possible to do that using a custom http interceptor in angular with minimal refactoring.

Ok it sounds as though you do not feel you are painting yourself into a corner which was the reason I brought it up.

jmirc commented 9 years ago

Fixed by #811