[seam-dev] JSF and CSRF

Pete Muir pmuir at redhat.com
Wed Mar 11 07:54:48 EDT 2009


On 11 Mar 2009, at 06:46, Dan Allen wrote:

> Christian's concerns are valid as demonstrated in the example  
> scenarios I provided. After giving it some thought, I believe I have  
> arrived at a solution, or perhaps at least the starting point of a  
> solution. I want to first comment on a couple of things, then get to  
> the proposed solution.
>
>
> 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.
>
> Also consider the case where the login information is submitted with  
> the POST (common on blogging sites) or the session just turned over  
> (meaning in another tab the user logged out and logged in again).
>
> 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.
>
> I think the best thing to do is just use a separate token which can  
> optionally bind to the session id when created.
>
>
> 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.
>
> You are correct. The complexity of the string is just smoke and  
> mirrors. The attacker only has to realize that it is the same  
> serialized string is used every time to realize he has found a back  
> door.
>
>
>
> 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
>
> As Christian points out, this solution is purely for scrambling the  
> View State and should not be mistaken for a secure token of any sort.
>
>
> (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.
>
> Actually, I think this is the right idea. My proposal builds on this.
>
>
> 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
>
> Either way, the predictable view identifier should be fixed.  
> However, once our token idea is in place, using a server-side view  
> state will just be gravy on top of the protection already in  
> place...meaning good for very secure and paranoid sites, but we can  
> still be reasonable secure without this approach.
>
>
> 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.
>
> Yes, build before restore should only be used once another secure  
> token strategy is in place. I am proposing one below. Even then, you  
> should only use this for more open sites (like blogs and forums) and  
> not for bank sites (which should always limit session length and be  
> paranoid about idle and reestablished sessions).
>
>
> So if my analysis is correct, JSF 1.x offers weak protection against  
> CSRF if you use server-side state saving, and nothing else.
>
> JSF 1.x is very hackable.
>
>
> 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:
>
> Here's what I came up with building on Christian's recommendations.  
> Feel free to offer improvements.
>
> [ UIToken / <s:token> ]
>
> My idea is to introduce a per-form secure token. Let me first  
> explain why it is secure, then how it works.
>
> The first step is to assign a unique identifier to the browser in  
> the form of a cookie (javax.faces.ClientUid). This is similar to the  
> session id, only it is established once for each browser session  
> (until the user closes the browser). As you may know, there is no  
> dependable way to identify a browser. So we are assigning it unique  
> identifier so that there is. This unique identifier will be used as  
> a salt when generating the token and can only be known by the  
> browser (never visible to an attacker under normal circumstances).
>
> The next step is to generate a token for the form, which is a hashed  
> version of the view signature. That hash is calculated as follows:
>
> sha1( signature = viewId + "," + formClientId, salt = clientUid )
>
> The developer can also choose to incorporate the session id into  
> this hash for a more secure token (at the cost of binding it to the  
> session)
>
> sha1( signature = viewId + "," + formClientId + "," + sessionId,  
> salt = clientUid )
>
> That token is then stored in a hidden field of the form
>
> <input type="hidden" name="javax.faces.FormSignature"  
> value="HASH_GOES_HERE"/>
>
> All of that is accomplished using the <s:token> component tag:
>
> <h:form>
>     ...
>     <s:token requireSession="true or false"/>
> </h:form>
>
> When the form is submitted, the token is checked in the decode()  
> method of UIToken. Here's how it is checked:
>
> 1. assert this is a postback, otherwise skip the check
> 2. assert that this form was the one that was submitted, otherwise  
> skip the check
> 3. get the brower Uid, otherwise throw an exception that the browser  
> must have a Uid (it should if it rendered a page having <s:token>)
> 4. get the javax.faces.FormSignature request parameter, otherwise  
> throw an exception that the form signature is missing
> 5. generate the hash as before and assert it equals the value of the  
> javax.faces.FormSignature request parameter, otherwise throw an  
> exception

do you mean assert?

>
>
> If all of that passes, we are okay to process the form (advance to  
> validate phase as decode() is called in apply request values)
>
> OPEN QUESTION: should we create a special exception for an invalid  
> form signature, like ViewExpiredException?

I would argue that we should create an exception that subclasses  
FacesException

>
>
> Please note that this token can be combined with client-side state  
> saving or the "build during restore" strategy to unbind a POST from  
> the session that created the view -- without sacrificing security.  
> However, it's still the most secure to require the view state to be  
> present in the session (JSF 1.2 server-side state saving).
>
> Feedback welcome. I'll follow up with a patch.
>
> -Dan
>
> -- 
> Dan Allen
> Senior Software Engineer, Red Hat | Author of Seam in Action
>
> http://mojavelinux.com
> http://mojavelinux.com/seaminaction
>
> NOTE: While I make a strong effort to keep up with my email on a daily
> basis, personal or other work matters can sometimes keep me away
> from my email. If you contact me, but don't hear back for more than  
> a week,
> it is very likely that I am excessively backlogged or the message was
> caught in the spam filters.  Please don't hesitate to resend a  
> message if
> you feel that it did not reach my attention.
> _______________________________________________
> seam-dev mailing list
> seam-dev at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/seam-dev

--
Pete Muir
http://www.seamframework.org
http://in.relation.to/Bloggers/Pete




More information about the seam-dev mailing list