[seam-dev] JSF and CSRF
Christian Bauer
christian.bauer at gmail.com
Tue Mar 10 09:31:07 EDT 2009
On Mar 10, 2009, at 01:10 , Dan Allen wrote:
> I have opened an issue in Mojarra about the build during restore
> feature that was/is planned for JSF 2.0, also mentioning the problem
> with the insecurity javax.faces.ViewState token that is used in
> server-side state saving. Feel free to comment or follow.
>
> https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=1028
>
> I agree that the token is completely insecure and predictable. But
> the build during restore is even scarier because all tokens go right
> out the window...we just have a plain old HTML form that can post
> into a JSF application.
>
> I think the insecure token can be fixed in JSF 2.0 and the build
> during restore should only be enabled only in JSF 2.1 when we have a
> secure token than can accompany it. The idea, I think, is to allow
> the client to store a representation of the view, but not
> necessarily the *whole* serialized view, opening the door for the
> view to be rebuilt on the server if it got dumped. It's sort of a
> hybrid of client- and server-side state saving. Am I off base?
What follows is a longer analysis and some of my (probably/hopefully
wrong) conclusions are worrying. I'd welcome any replies and
corrections before I'll merge this with the other CSRF wiki pages. I
think we need a clear strategy here that everyone understands and
agrees on.
We have two contradicting objectives, we can't have it all
automagically.
1. You want forms on a webpage that are submitted via POST,
independent of any session state on the server. Typically these are
login or comment forms, or any view that users might keep open for a
long time until the session expires, and then trigger a POST request
that should be valid.
2. You don't want POST requests accepted by the server that execute an
action without any forgery protection. Otherwise you are vulnerable to
CSRF attacks. Common remedies are bound to the user's session in one
way or another. The best protection is offered by strong random tokens
in hidden form fields that are validated against the stored value in
the user's session. Weaker but still acceptable protection would be
duplicate sending of the session identifier - in the cookie by the
browser AND as a request parameter or hidden POST data. An attacker
would have to guess either the random token value or the session
identifier to forge a request.
JSF (and probably Seam) need to offer you tools so you can balance
these goals on a per-case basis.
First consider JSF 1.x:
You can globally configure client-side state saving. If you are saving
the view serialized on the client, and the server is configured to
accept this serialized state of a view without any further privilege
validation, your application is vulnerable to CSRF. So while you get
objective 1, it completely ignores objective 2. Worse, it's a global
switch that affects _all_ of your forms/views. Also, it doesn't matter
that the client state is saved in a "complex" hidden form value, an
attacker can forge that too. The server will accept it as long as it's
well-formed and can be deserialized.
To protect against tampering (!NOT! CSRF) the Sun JSF RI offers
encryption of the client-side state with a configurable global
password. See http://wiki.glassfish.java.net/Wiki.jsp?page=JavaServerFacesRI#section-JavaServerFacesRI-HowCanISecureViewStateWhenUsingClientSideStateSaving
- The issue here is that the view state cyphertext in the hidden
form field also comes with at least part of the original plaintext,
making it easier to crack the global password, see last comment on https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=906
(I never really bothered with client-side state saving so the
following might be wrong.) I don't think client-side state saving can
be made secure against CSRF attacks without losing its main benefit.
Consider the following "replay" attack procedure:
- Attacker goes to vulnerable website and grabs the encrypted view
state of the hidden field in a comment form.
- Attacker goes to vulnerable website and posts a new comment, linking
to his own website.
- Victim goes to the vulnerable website and follows the link to the
attackers website.
- Victim clicks on a button on the attackers website.
- A forged POST request will be send to the vulnerable website, riding
the victims still active session.
- The forged POST request contained the valid encrypted view state
(decrypted with a global password), and the attackers data.
The only protection against this kind of attack would be a session
identifier included in the view state (or at least in the request
outside of the cookie), which means we don't get session independence
of the form/view! Another protection would be globally incremented
component identifiers, so that an old view state with "lower" values
than the current can not be replayed. This might be a way out but I'm
not sure at this point how secure it really is. After all, developers
can assign their own client component identifiers for e.g. JavaScript
access.
You can globally configure server-side state saving. The server will
throw a ViewExpiredException if a POST request arrives without a valid
(present in session) view identifier. This completely ignores
objective 1 but offers some protection against CSRF. Unfortunately,
the JSF RI generates a predictable view identifier. This needs to be
fixed, see https://javaserverfaces.dev.java.net/issues/show_bug.cgi?id=812
You can globally configure Facelets BUILD_BEFORE_RESTORE option for
server-side state saving. This, like client-side state saving, gets
you objective 1 but again on a global basis. Worse, the attacker
doesn't even have to forge the potentially complex serialized view
state, break the encryption, or mount a replay attack. Definitely not
recommended.
I don't consider page actions, @WebRemote or even REST request
processing here, the issues for these are similar.
So if my analysis is correct, JSF 1.x offers weak protection against
CSRF if you use server-side state saving, and nothing else.
For JSF 2.x this is what we need: A _non-global_ per-form switch to
define how the view state should be restored. These would be my
recommendations:
- Global client-side state saving should not be enabled by default.
Only if a POST request contains the session identifier (in the view
state data or as a hidden form field, NOT COOKIE) will client-side
state saving be secure against CSRF. Encryption alone does not
protect. Client-side state saving CAN NOT make POST requests
independent of the user's session and still offer CSRF protection. So,
client-side state saving should not be considered for security
reasons, but only to improve performance in certain scenarios (small
session state on server, free bandwidth on intranet, etc). There
should be no difference in behavior between client- and server-side
state saving when a session expires on the server.
- This should be the global default: Server-side state saving with a
mandatory strong token on all POST requests. Server-side state saving
should be protected with a per-request token which acts as a secondary
session- (or per-form) identifier that is not transmitted
automatically by the browser (hidden form field, not cookie). The
token value should be cryptographically strong to prevent brute force
attacks. The ViewExpiredException is thrown if the view can't be
restored from session state. If the JSF 2.x specification does not
have this default, Seam needs to offer it automatically on top of JSF,
to make it secure.
- The framework (JSF or Seam) has to offer, on a per-form or per-view
basis, the BUILD_BEFORE_RESTORE option. That way, when I have an
idempotent action or what I consider safe form on a page, I can make
it independent of the current user's session state. I'd typically do
this for the minority of my forms in a real web application, probably
the login form, the search form, and the comment form. Note that if my
analysis of client-side state saving is correct (session affinity),
this feature is completely independent of what is configured globally.
- Other request processing such as @WebRemote and ("stateful") REST
would have to be secured with a per-request token that is checked
against the token in the user's session. It's debatable whether this
should be the default or if I'd need to enable it for each method/
request path. It's probably OK to tell users that these requests
should be idempotent and if they want to delete their database with a
@WebRemote method, they should protect it properly. REST processing is
a different matter, here we have to deal with a potentially stateless
server tier, no user sessions. (This issue is apparently being ignored
by REST proponents. Rails also offers only a session-stored hidden
form field token.) Maybe it's good enough if we'd implement the weaker
CSRF protection for both @WebRemote and stateful REST: Not a per-form
random token, but an additional transmission of the session identifier
outside of a cookie (see next item).
- GET request processing of page actions should IF AT ALL POSSIBLE be
idempotent requests, however, Seam CAN offer double validation of the
session identifier (once in the cookie, again as an extra request
parameter). It would be better to tell users that page actions should
be "read-only" methods.
Dan, if you compare these recommendations to what is/will be in JSF
2.0 and the RI, I think the last three are missing and we need to
deliver them in Seam.
More information about the seam-dev
mailing list