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-Ja...
- 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
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(a)lists.jboss.org
https://lists.jboss.org/mailman/listinfo/seam-dev