Hi
Checking the latest spec and trying to understand in deep how each feature
should work, I notice some parts in JSF 2.2 spec section 2.2.1 related to
view protection that needs to be clarified a little more.
Here is what it says:
"... If the request is not a postback ... Call
ViewHandler.getProtectedViewsUnmodifiable() to determine if the view for
this viewId is protected.
- If not, assume the requested view is not protected and take no
additional view protection steps.
- Otherwise, look for a Referer [sic] request header.
+ If the header is present, use the protected view API to determine if
any of the declared protected views match the value of the Referer header.
* If so, conclude that the previously visited page is also a protected
view and it is therefore safe to continue
* Otherwise, try to determine if the value of the Referer header
corresponds to any of the views in the current web application.
x If not, throw a ProtectedViewException.
+ If the Referer header is not present, fall back on inspecting the
incoming URL.
Obtain the value of the value of the request parameter whose name is
given by the value of ResponseStateManager.NON_POSTBACK_VIEW_TOKEN_PARAM.
If such a request parameter value is not present, throw
ProtectedViewException. If the value is present, compare it to the return
from ResponseStateManager.getCrpytographicallyStrongTokenFromSession(). If
the values do not match, throw ProtectedViewException. ..."
The problem is that Referer request header is vulnerable to referer spoofing
attack. See:
http://en.wikipedia.org/wiki/Referer_spoofing
In few words it says:
"... referer spoofing is the sending of incorrect referer information in an
HTTP request in order to prevent a website from obtaining accurate data on
the identity of the web page previously visited by the user. ..."
In my understanding, the idea behind CSRF protection in JSF is just provide
a query param that is included in all protected urls in a automatic way, but
the intention is avoid to include it for POST request because in those cases
javax.faces.ViewState param do the same job.
The value of the query param is a "secret" that is usually stored into
session. ResponseStateManager.getCryptographicallyStrongTokenFromSession(
FacesContext context) do that part of the job.
The problem start in these words:
"... Otherwise, try to determine if the value of the Referer header
corresponds to any of the views in the current web application. ..."
Think in this example:
GET /form.jsf
POST /form.jsf --> /form.jsf
GET /protectedView.jsf?javax.faces.Token=mysecret
GET /anotherProtectedView.jsf?javax.faces.Token=mysecret
The idea is that when /form.jsf is rendered and it is detected a link that
belongs to a view that is protected (it match any url pattern as defined
in servlet spec like /form.* or /module/* or /form.jsf), the token should
be added to the link, so when the GET is done, the url has the secret.
The point is the only thing that is really protecting the view is the
"secret" sent in the query param "javax.faces.Token". I notice between the
POST and the GET, the browser (firefox 18.0.2) fills Referer header. So,
according to the algorithm specified:
GET /form.jsf
POST /form.jsf --> /form.jsf
GET /protectedView.jsf?javax.faces.Token=idontknow
Referer :
https://localhost:8080/form.jsf
Is /protectedView protected? yes, check Referer header, any of the declared
protected views match the value of the Referer header? no, so try to
determine if the value of the Referer header corresponds to any of the views
in the current web application. It matches? yes, so continue and trust
blindly on what Referer says.
The browser (firefox) really do this:
GET /form.jsf
POST /form.jsf --> /form.jsf
GET /protectedView.jsf?javax.faces.Token=mysecret
Referer :
https://localhost:8080/form.jsf
GET /anotherProtectedView.jsf?javax.faces.Token=mysecret
Referer :
https://localhost:8080/anotherProtectedView.jsf?javax.faces.Token=mysecret
When a protected view can be accessed through GET without javax.faces.Token?
Never! because the whole point is use it as a key to "mitigate" CSRF attacks.
But add the token in GET has its implications (see [1] for details).
In this wiki:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
This is what is says about Checking The Referer Header:
"... Although it is trivial to spoof the referer header on your own browser,
it is impossible to do so in a CSRF attack. Checking the referer is a commonly
used method of preventing CSRF on embedded network devices because it does
not require a per-user state. This makes a referer a useful method of CSRF
prevention when memory is scarce.
However, checking the referer is considered to be a weaker from of CSRF
protection. For example, open redirect vulnerabilities can be used to exploit
GET-based requests that are protected with a referer check. It should be
noted that GET requests should never incur a state change as this is a
violation of the HTTP specification.
There are also common implementation mistakes with referer checks. For example
if the CSRF attack originates from an HTTPS domain then the referer will be
omitted. In this case the lack of a referer should be considered to be an
attack when the request is performing a state change. Also note that the
attacker has limited influence over the referer. For example, if the victim's
domain is "site.com" then an attacker have the CSRF exploit originate from
"site.com.attacker.com" which may fool a broken referer check implementation.
XSS can be used to bypass a referer check. ..."
So, it is valid to check Referer header if present (just the usual: server,
port, context path, that the view exists ...), but the Token must be check
always for non postback requests (remember postback contains
javax.faces.ViewState token). Inclusive, we could check for the Origin
Header if present just for consistency suggested in the owasp.org wiki.
regards
Leonardo Uribe
[1] In this wiki:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
It says this about Disclosure of Token in URL:
"... While this control does help mitigate the risk of CSRF attacks, the
unique per-session token is being exposed for GET requests. ....
... Timeouts on CSRF tokens are very unlikely to help this attack scenario....
... The ideal solution is to only include the CSRF token in POST requests and
modify server-side actions that have state changing affect to only respond
to POST requests. This is in fact what the RFC 2616 requires for GET
requests. If sensitive server-side actions are guaranteed to only ever
respond to POST requests, then there is no need to include the token in
GET requests. ...
In most JavaEE web applications, however, HTTP method scoping is rarely ever
utilized when retrieving HTTP parameters from a request. Calls to
"HttpServletRequest.getParameter" will return a parameter value regardless
if it was a GET or POST. This is not to say HTTP method scoping cannot be
enforced. It can be achieved if a developer explicitly overrides doPost()
in the HttpServlet class or leverages framework specific capabilities such
as the AbstractFormController class in Spring.
For these cases, attempting to retrofit this pattern in existing applications
requires significant development time and cost, and as a temporary measure it
may be better to pass CSRF tokens in the URL. Once the application has been
fixed to respond to HTTP GET and POST verbs correctly, CSRF tokens for GET
requests should be turned off. ..."