5

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

Vasil Lukach
  • 3,658
  • 3
  • 31
  • 40
Daniel
  • 36,833
  • 10
  • 119
  • 200
  • 1
    FYI: this is specified/expected behavior for client side state saving. Only unsure how to answer "how can I make sure user wont be able to reuse ViewState" for MyFaces other than switching back to server side state saving. Also, CSRF possibility is a bit exaggerated as the view state basically contains component state, not logged-in user and other session data. It all depends on the form, e.g. if it's some admin form for user management, the attacker could prepare some state (e.g. assign himself admin role) and have the admin user submit it, but still you need a XSS hole and/or session hijack. – BalusC May 27 '15 at 16:25
  • 1
    Shouldn't the viewstate ID die with the session @BalusC? – kolossus May 27 '15 at 16:27
  • Only with server side state saving. See also a.o. http://stackoverflow.com/a/3642969 – BalusC May 27 '15 at 16:28
  • @BalusC, I understand that it, for current implementations, only 'dies' with server side statesaving, but if the view state id (just the id) is also put in a session scoped 'valid viewstate id' list, this issue is solved. I'd not consider it a 'bug' or real 'security issue' but an 'omission' that can be 'improved'. – Kukeltje May 28 '15 at 16:56

2 Answers2

6

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:

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the answer, so you are saying that if I keep using client side state saving, the only way I will be able prevent a scenario that I described in my question is to generate my own *csrf* token for each session and check upon every client -> server request if the token that was used is equal to current token of that session? – Daniel May 27 '15 at 19:01
  • That would indeed be your best bet if you want to keep client side view state saving. And, make sure that you don't have a XSS hole and that the webapp is served over HTTPS. – BalusC May 27 '15 at 19:15
  • Ok, thanks, is there a way to inject a token to all my *POST* submits from client to server, I don't want to modify all my `h:form`, instead I prefer a single point entry in which I will inject a token to the `form` and then on submit will test against the token on server – Daniel May 28 '15 at 07:54
  • Override `h:form` renderer. Write token in encode and validate it in decode. Be careful with `partialSubmit="true"` in case you're using PrimeFaces, it would skip the token if you don't explicitly specify it. – BalusC May 28 '15 at 07:58
  • Thanks, I an using PF, how should I specify it explicitly for primefaces? (I can open a new Q' if you prefer? – Daniel May 28 '15 at 08:24
  • Thinking about it, you could reuse the `h:form`'s second hidden field for that, that one with `name="formId" value="formId"` (which indicates which form is currently being submitted). I didn't check it, but I believe PF will autoprocess it regardless. If so, you could override the renderer to let the value be a CSRF token. – BalusC May 28 '15 at 08:37
-1

Spring Security does not check GET request as default. In CSRF protection, it is important to prevent dangerous POST request manipulation. Therefore, you may consider to ignore ajax partialSubmit="true" requests with following code set.

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        RequestMatcher csrfRequestMatcher = new RequestMatcher() {
            private final Pattern allowedMethods = Pattern.compile("^GET$");
            @Override
            public boolean matches(HttpServletRequest request) {

                if(request == null)
                    return false;

                if (allowedMethods.matcher(request.getMethod()).matches()) {
                    return false;
                }

                else return request.getParameter("javax.faces.partial.ajax") == null || !request.getParameter("javax.faces.partial.ajax").equals("true");
            }

        };

        http
            .anyRequest().authenticated()
            .....
            .csrf().requireCsrfProtectionMatcher(csrfRequestMatcher);
    }
}