All,

I've been playing with this Filter recently, and I have some concerns for its practical use. I'm considering adding some features to it in order to make it more practical to use, and I'm interested to see what others think about these "problems" and whether my potential additions have merit.

These are in no particular order (e.g. importance).

1. Killing caching

If used as prescribed, all URLs will have CSRF tokens added to them even when they are unnecessary. Specifically, I'm thinking about static resources such as images, scripts, and CSS.

A sample page I'm using for testing uses a standard template for the web application I'm working with, and it normally requests 3 CSS files, 6 Javascript files, and a site-logo. The browser very much likes to use the "favicons" that are defined in the page, and so it (Firefox) will load 2 different sizes of favicon for various purposes.

Simply clicking into the title bar and pressing ENTER shows that /all of those resources are re-fetched from the server for every request/. The browser cache has been defeated.

Each of these resources has been configured to return the following HTTP response headers to encourage caching:

Cache-Control: public
ETag: [the etag, which is constant]

The same of course happens if you perform a "quick" page reload (i.e. CTRL-R/CMD-R/click-reload-button) rather than performing a FULL page reload (i.e. SHITF-CTRL-R/SHIFT-CMD-R/SHIFT-click-reload-button).

This happens because every page-access generates a new CSRF token for that request which is used for all the URL references on that page.

Intermediate caches such as true caching HTTP servers will likely have the same problem. For caching HTTP servers, this represents a mild DOS attack since those caches will be wasting their cache space on one-time URLs. They could surely be tuned to ignore query-strings for certain URL patterns, but then cache performance (i.e. speed) will be decreased due to that URL-processing being added to the pipeline.

I think it might have some sense to add some filtering to the CsrfResponseWrapper (which is where HttpServletResponse.encodeURL and HttpServetRequest.encoreRedirectURL are hooked to add these token parameters to the URLs) to allow it to STOP adding tokens to certain URL patterns. Prefix (/images/*) or suffix (*.css) matching seems to make sense to me for inexpensive checks to get caching working again.

2. <form>s with either GET or POST. Or, really... "Forms"

When using <form method="get" action="/foo?csrf=blahblah">, different browsers do different things with the query string. I'm mostly testing on Firefox and I don't have time to check this on every browser. It's enough that Firefox behaves in this way.

<form method="get" action="/foo?csrf=blahblah">
  <input type="submit" name="btn" value="Go!" />
</form>

Submitting this form causes a 403 response. The browser shows that the URL requested from the server is /foo?btn=Go! and the CSRF token has been removed from the action. (Surprise!) I don't see anything in WHATWG's description of how form-submission is supposed to work that indicates that GET versus POST should behave differently with respect to the 'action' attribute, but this is what the current version of Firefox actually does.

There are three options I can think of to get around this:

a. Switch to POST:

<form method="post" action="/foo?csrf=blahblah">
  <input type="submit" name="btn" value="Go!" />
</form>

You get HTTP POST /foo?csrf=blahblah with an application/x-www-urlencoded entity containing the form <input> element(s).

b. Manually add the CSRF token to the form:

<form method="get" action="/foo?csrf=blahblah">
[ if request.getAttribute("org.apache.catalina.filters.CSRF_NONCE_PARAM_NAME") ] <input type="hidden" name="csrf" value="[ request.getAttribute("org.apache.catalina.filters.CSRF_NONCE_PARAM_NAME") ]" />
[ end-if]
  <input type="submit" name="btn" value="Go!" />
</form>

In this case, you get a GET request with the csrf parameter in there because, well, you asked for it.

c. Hack the CsrfPreventionFilter to allow all POST methods. This is one of my proposals: allow users to configure the CSRF prevention filter to always-allow HTTP POSTs. This cannot be done in web.xml using e.g. <http-method>GET</http-method> for the <filter> because Filters cannot be configured in this way. And if they could be, the Filter would not be invoked and all the URLs on your page would lack the required CSRF tokens to make /those/ links work.

3. Default token-cache size

The default token cache size is 5. If you use applications like I do, you often open multiple links into separate tabs for various reasons. Do a search of products in your application, and there are several of them you'd like to get details for. So you middle-click (or whatever does it in your environment) on each of those links to open separate tabs, leaving the search-results page visible in the current tab.

Then you want to click the "next" button to view the next page of the results. An HTTP 403 response is returned because, if you opened more than 4 extra tabs, the tokens used on the search-results page have already expired, and you can't use your page anymore.

I'm not sure what the best token cache size is. It might depend upon the behavior of your users. If I use your site, you might want a very large token cache size. But I think maybe 5 isn't quite big enough. At least it's trivially settable, so I don't really have a recommendation here other than "maybe let's set the default to something higher"?

4. No "test mode"

It might be really helpful to have a non-enforcement mode that generates logging without any errors. This would allow developers to enable the CSRF prevention filter in a mode where the nonces are generated and used, but only checked and logged. Any time a token would have been rejected, it is logged but the request is allowed.

The logging is already there, but requests are rejected.

I am having a problem at $work where anytime I enable this in a development environment, I get loads of complaints from users being locked-out of things that I haven't had the time to test and/or mitigate (such as <form> submissions). If there were a non-enforcement mode, I could enable it, then look at the logs after a week and fix anything I find in there without disturbing anybody.

5. All or nothing

This may be 4(b) instead of "5", but...

When enabling the CSRF prevention filter, it's impractical to configure it for anything but <url-pattern>/*</url-pattern>. If you choose any other filter-pattern, you will find that you need numbers of "entry points" which are request URLs that are allowed to be requested /without/ providing a CSRF token.

This means that your *entire application* needs to be working perfectly in order to enable the CSRF prevention filter.

It would be nice to allow enforcement of, for example, subsets of the URL space. Maybe I only want to enforce the CSRF tokens in my /admin/ and /product/ URL spaces, allowing the /public/ and /whatever/ spaces to remain unprotected.

But if I visit /public/index and want to click on a link pointing to /admin/index, it's got to have that CSRF token in it otherwise I get an error. So I can't just use <url-pattern>/product/*</url-pattern> and <url-pattern>/product/</url-pattern> because I will not only get non-enforcement for those other URLs, I will also get no tokens.

Providing one or more non-enforcement URL spaces (prefixes?) would allow users to enable CSRF mitigations on parts of their web applications, test and fix anything that isn't working, then move on to other parts of the application, adding enforcement a chunk at a time.

I'm not sure what others' experiences od using the CSRF prevention filter have been like, but my experience so far has been very rocky as you can see from the issues I'm raising above.

I'd be very happy to receive feedback on anything I've written above.

-chris

[1] https://html.spec.whatwg.org/#concept-form-submit

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to