[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