jsf-2viewstatejsf-2.2myfaces

Reusing ViewState value in other session (CSRF)


I'm using a *myfaces-api-2.2.3 with javax.faces.STATE_SAVING_METHOD set to client ,

I got the following scenario,

1) User X logs into the system and adds user XXX (using jsf f:ajax action) , while inspecting the chrome dev tools you can see the form that being submitted along with the ViewState value.

2) Copy that ViewState value (from chrome dev tools --> network tab) --> place it into html file with form (that mimics my original add user X)

3) Logout from user X session (session being invalidated during that process)

4) Login with user Y --> open that html file in browser and hit the submit button of the form --> you will notice that user XXX was added (despite the fact that the ViewState value that was used in form belongs to other user (User X).

I thought that the ViewState value can't be used in that way and I thought that it should prevent this kind of actions, how come it is possible to use one ViewState value in a brand new session that holds its own ViewState value and how can I make sure user wont be able to reuse ViewState?


See my other question and BalusC answer : Prevent CSRF in JSF2 with client side state saving


Solution

  • 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: