This is specified/expected behavior when using client side state saving. The JSF view state is not saved in the session, but in its entirety in a hidden input field in the HTML form in the client side. Only when using server side state saving, the JSF view state will invalidate when it doesn't exist in user's HTTP session.
I'm not seeing any way in MyFaces to invalidate the client side saved state in case it's reassociated with the "wrong" session during postback. The org.apache.myfaces.CLIENT_VIEW_STATE_TIMEOUT
context param comes close. You could set it to same time as session timeout.
The CSRF attack scenario is IMO a bit exaggerated. First, the attacker need to be able to get a hand of the client side state. The easiest way for that is a XSS hole. Only, JSF has XSS attack prevention everywhere (if you're careful with escape="false"
). The hardest way is to have a hand of the entire HTML output. This could be done by a man-in-the-middle attack. Only, this also gives the attacker the whole session cookie, so the whole CSRF attack scenario in that case is "unnecessarily overcomplicated" for the attacker, as the attacker could simply hijack the session. The best protection against that is simply using HTTPS instead of HTTP.
Even then, if the attacker has got a hand at the client side view state somehow, the attacker can't do much hazardous things with it without decrypting, unserializing and manipulating it. MyFaces encrypts it by default for long time (already since early 2.0.x). The attacker can't decrypt it without having the right key and therefore also not manipulate it. This client side view state encryption has by the way become a required part of JSF 2.2 specification (so Mojarra also does it). This key gets by default reset when you restart the application server (and thus also all client side states will be invalidated). You can if necessary specify a fixed key by below environment entry in web.xml
:
<env-entry>
<env-entry-name>jsf.ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
You can use this page to generate a random AES key in Base64 format.
Even then, if the attacker somehow succeeds to decrypt the view state and got another user to actually submit it (e.g. via a XSS hole or a phishing site), then your best bet is to include a HTTP session based CSRF token in the JSF page. But also that is not safe if the webapp has still a XSS attack hole somewhere, or is served over HTTP instead of HTTPS.
See also:
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…