On 13/12/2023 22:05, Christopher Schultz wrote:
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.
The don't map the filter to those resources? Ah, I've now read you
comment below.
It sounds like we do need a more sophisticated functionality to URL
mapping than we can get with just the filter mapping.
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.
Doesn't option c) defeat the purpose of the CSRF Filter? It looks like
a) or b) are the better choices here.
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"?
There is a trade-off here with memory usage but I wouldn't object to a
higher default. Users with memory pressure concerns can always lower it.
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.
+1
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.
I'm not clear how this would work. Don't you "just" need to define entry
points for the parts that are protected? You'd need to do that anyway
wouldn't you?
Mark
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org