ninja icon indicating copy to clipboard operation
ninja copied to clipboard

CSRF protection for put post delete

Open raphaelbauer opened this issue 13 years ago • 13 comments

Like in play1 or in django: csrf https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-csrf-works

=> a form must have a token that can be validated

raphaelbauer avatar Aug 16 '12 19:08 raphaelbauer

Some more information copied from google groups: https://groups.google.com/forum/#!topic/ninja-framework/KU4Hr6_3nZ8

The basics are already implemented:

  • Ninja session cookie can generate a unique id.
  • Don't allow javascript to read the session => That's the basic line of defense...

What is missing is:

  • A simple method to include the unique id into a form (if it's a form request for instance) => A tag or something to include into Freemarker rendering.
  • A filter to check whether the post request is valid. If the request parameter of the form is identical to the id in the cookie the request is not forged.

In one of our projects I wrote some custom code to do that, but we should include that into the framework. As I said above I'll attach some code to the issue.

raphaelbauer avatar May 01 '14 08:05 raphaelbauer

Some code:

AuthenticityFilter: public class AuthenticityFilter implements Filter {

public static final String AUTHENTICITY_TOKEN = "authenticityToken";

@Override
public Result filter(FilterChain filterChain, Context context) {

    Session session = context.getSession();
    String authenticityToken = context.getParameter(AUTHENTICITY_TOKEN);

    if (session.getAuthenticityToken().equals(authenticityToken)) {

        return filterChain.next(context);

    } else {

        return Results
                .forbidden()
                .template(NinjaConstant.LOCATION_VIEW_FTL_HTML_FORBIDDEN);

    }


}

}

raphaelbauer avatar May 01 '14 08:05 raphaelbauer

The filter assumes your request contains a parameter called "authenticityToken". Authenticity token is generated and stored in the session via session.getAuthenticityToken() (already implemented).

But you have to include the parameter either via a query or post parameter right now..

raphaelbauer avatar May 01 '14 08:05 raphaelbauer

How to display the token in the view ? now I'm using ${session.___AT} it it the right way ?

example :

<a href="${reverseRoute("controllers.DashboardController","logout","xsrf",session.___AT)}">logout</a>

noguespi avatar Oct 08 '14 07:10 noguespi

hmm... in theory yes. But I think that's imperfect. getAuthenticityToken creates a new token when it's absent. session.___AT (in your view) does not do that.

So if you just include ${session.___AT} in your views you have to make sure that somewhere before in your controller session.getAuthenticityToken() has been called. Otherwise your view.ft.html will complain :)

As I write above: "Missing: A simple method to include the unique id into a form (if it's a form request for instance) => A tag or something to include into Freemarker rendering."

If you want to contribute let us know!

raphaelbauer avatar Oct 08 '14 18:10 raphaelbauer

Yes I noticed this issue, so what I'm doing is calling session.getAuthenticityToken upon user login or session generation.

I'm newbi with freemarker, why can't I call session.getAuthenticityToken from the template ?

noguespi avatar Oct 12 '14 12:10 noguespi

That's because it's implemented as map and not as object.

Have a look at: https://github.com/ninjaframework/ninja/blob/develop/ninja-core/src/main/java/ninja/template/TemplateEngineFreemarker.java

lin 239: if (!context.getSession().isEmpty()) { map.put("session", context.getSession().getData()); }

That puts the map of session into your Freemarker template. Therefore you cannot access getAuthenticity token.

There may be better ways to do this - contributions are welcome :)

raphaelbauer avatar Oct 21 '14 20:10 raphaelbauer

I added this to my project, looks good!

@raphaelbauer Could a freemarker variable be set up to contain the token automatically? Then you just need something like

in your form, mark the controller method as FilterWith and you are done.

I wonder if POST methods should be filtered this way automatically though. That way, you are secure by default. I suppose, a controller can always be annotated with @NoXSRFProtect or equivalent if need be.

mikehearn avatar Oct 24 '14 16:10 mikehearn

Of course github ate my markup instead of escaping it .... sigh.

The missing bit was a hidden HTML input field like [ input type="hidden" name="atoken" value="${atoken}" ]

mikehearn avatar Oct 24 '14 16:10 mikehearn

Yes... totally agree - and that's really missing.

I guess there should be

  1. a helper that generates input type="hidden" name="atoken" value="${atoken}" ]
  2. A simple way to get hold of the session authenticity token. Best would be to inject the current session into the freemarker template. Then you can call session.getAuthenticityToken.

Are you interested in implementing something? We can certainly help :)

raphaelbauer avatar Oct 24 '14 17:10 raphaelbauer

Maybe! My project is (a) really small and (b) behind where I should be :) so, not sure if I'll have time given that I probably only have two or three forms to XSRF protect. But I like Ninja so will dig in and see if I can make a patch in the coming weeks.

mikehearn avatar Oct 27 '14 15:10 mikehearn

Cool... Let us know if we can help :)

raphaelbauer avatar Oct 27 '14 21:10 raphaelbauer

Any update on this issue?

What about just passing the authenticity token to the template like e.g. prettime.

In TemplateFreemarkerEngine one could just add

   map.put("token", context.getSession().getAuthenticityToken());

and in the template just call

${token}

Thats what I just tested and it worked. Of course, this has to be done in a cleaner with, returning the complete form.

svenkubiak avatar Feb 06 '15 10:02 svenkubiak