Help

Built with Seam

You can find the full source code for this website in the Seam package in the directory /examples/wiki. It is licensed under the LGPL.

An CSRF/XSRF attack misuses the trust of a site towards a user. An attacker abuses the login state of a user to trigger privileged actions offered by the web site. For this purpose an adversary needs to manipulate the request flow between user and web site with forged requests.

A vulnerable web application executes actions that are triggered by requests, with only a trivial check of the current users session and privileges. For example, if an application deletes a row in a database if it receives the GET request http://example.com/delete.action?rowId=1234, it is vulnerable to an XSRF attack if it only checks the current users privileges. The current users privileges are present in the current users HTTP session, and the session identifier cookie will automatically be send by the victims web browser with any request. The attacker only needs to trick the victim or the victims browser into executing that request, maybe in a hidden frame or browser window.

Cause

Sites that are vulnerable to this problem typically have difficulties to distinguish regular (in-session) requests from one-off requests forged by an attacker. If a web site does not enforce a per-request authentication or authorization (such as context-dependent transaction number) method, an attacker is able to execute privileged actions in the context of the currently logged-in user.

Exploitability

The attack may begin with a social engineering action in order to forge an HTTP request that triggers a privileged action. A typical victim is an administrator using a web-based remote management software. The attacker prepares a link that will trigger the privileged action and may embed it in an email or hide it on his website in an invisible layer on top of an unsuspicious clickable element. Furthermore, this link might be directly injected into the target website if the target is also vulnerable to Cross Site Scripting. Malware is an additional source to trigger requests that exploit CSRF vulnerabilities.

As an example, the attacker may trick the logged-in user onto a password change field, to replace the current password of the administered application with one of his choosing. An additional scenario seen in practice is to log off the user from his account and silently relogin him to a fake account (known to the attacker) to later harvest the data the user entered. Certain web sites also cache unauthenticated web requests and replay them silently when the user has logged in, which is an additional cause for CSRF to happen. Recent click-jacking attacks are a variant of CSRF, allowing to misuse a vulnerable version of the Flash advertisement player to hijack clicks to other webpages or silently activate web cams and microphones.

Unfortunately a lot of web-sites offer a Remember Me on this computer feature, which dramatically increases CSRF exploitability. If, for example, a Remember Me cookie that authenticates the victim automatically will be send with every request, the user is vulnerable even if the target website is not open in her browser. Any malicious website or e-mail can then trick the victim into clicking a link that executes a privileged and automatically authorized action.

Possible Remedies

Users should log off from an application as soon as the privileged actions have been completed. The less time the user spends in “privileged” mode, the less vulnerable the user is. Bookmarkable logoff buttons, or logout keyboard shortcuts increase the usability of this precaution. On the other hand, a Remember Me feature contradicts this goal. Users should be encouraged to utilize the Remember Password feature of their browser instead, so that they can easily log-in with one click, with a pre-filled login form. This provides a balance between security - no fully automated authorization will occur - and convenience. On modern operating systems, login data and passwords remembered by browsers will even be stored encrypted in a central keychain or wallet.

On the server side some half-solutions have been proposed, such as allowing POST requests only, to prevent drive-by changes triggered by simple GET link clicks. Of course, POST requests can also be forged, so this solution is not sufficient. Also, referrer checking does not provide a complete solution, as intermediate web proxies or client security products may block certain HTTP headers such as referrers. Referrer checking does also not block attacks which are caused by cross site scripting problems, with malicious links injected directly into the target web site.

A valid defense against CSRF is providing web pages that are not vulnerable to Cross Site Scripting in combination with a shared secret (optionally per-request individual token) to trigger privileged actions and allow a “NoScript” usage. A web page that works without client side Javascript logic significantly lowers the attack surface. If attackers are able to inject Javascript through a Cross Site Scripting vulnerability, they can overwrite certain Javascript handlers with their own setters and getters, which again allows to gain access to the valuable login cookie.

Impact from Cross Site Scripting

A Cross Site Scripting vulnerability would allow an attacker to circumvent most CSRF protection mechanisms. If you are protecting forms on a web page with a unique transaction token, an attacker can inject Javascript code on that page and read the token value, then forge a valid but malicious request.

Considerations for Seam framework users

Seam and JSF allow immediate execution of actions triggered by any HTTP request, bet it GET or POST, and even DELETE and PUT.

Protecting JSF postbacks

JSF tries to ensure that an event is legitimate by requiring a serialized representation of the component tree to be present at the time the form is submitted. Where that component tree is stored depends on the type of state saving that is in use. In client-side state saving, the serialized component tree is stored in the hidden form field javax.faces.ViewState and submitted along with the form. In server-side state saving, the serialized component tree is stored in the user's session and an auto-generated identifier token, passed through the hidden form field javax.faces.ViewState, is used to identify the component tree object in the users server-side session.

Consider the following simple JSF form in an XHTML template:

<h:form>
    <h:commandButton action="#{foo.doStuff}" value="Do Stuff"/>
</h:form>

This form will be rendered as HTML:

<form id="j_id4" name="j_id4" method="post" action="/myform.seam" enctype="application/x-www-form-urlencoded">
    <input type="hidden" name="j_id4" value="j_id4" />
    <input type="submit" name="j_id4:j_id8" value="Do Stuff" />
    <input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="j_id3"/>
</form>

Here we are assuming the use of server-side state saving. The important part - the per-request security token - is the second hidden field: When the form is submitted, the value j_id3 will be transfered to the server in a POST request. The server will now first validate the users current HTTP session, which has been identified through the automatically transmitted cookie. Inside that session, the view state with the identifier j_id3 has to be present, or the request will be aborted. This validates that the request originated from the form that has been presented to the user, and has not been forged by an attacker and that the request is not simply riding the existing HTTP session.

Unfortunately, the view state identifier is an automatically generated incremented value and quite easy to guess by an attacker. The value is always:

'j_id' + a sequential number

where the sequential number is the number of views in the user's session (always starting at 1). Thus, it's possible to coerce an unassuming user with a valid session into submitting a form of the attackers choosing.

A better solution would be a cryptographically secure pseudorandom number. As this is dependent on the JSF implementation (this example was created with the Sun JSF RI), a more secure CSRF protection for POST requests is possible with specification compliance.

Going beyond the use of a random number of the view state token, the most appropriate way of securing a postback is to use a public-private key cryptology negotiation. See Seam's <s:token> component tag for an explanation of this solution.

Even worse, client-side state saving is extremely easy to hijack since the component tree is stored on the client. All the attacker needs to have is the signature of the component tree and he can replay and send a valid POST request to the server. The server will restore the view by deserializing the data sent by the client and execute the action. We recommend that you do not use client-side state saving without additional per-request security tokens, see Seam's <s:token> tag.

Build on demand of the JSF component tree

In client-side state saving, the restore view step is independent of the user's session. All the state necessary to rebuild the view and process the user-initiated event is sent along in the form POST request. The user could load the form on one day and submit it days later assuming that other session data isn't required (e.g., a login or comment form usually works without requiring a logged-in session).

This portability is not present in server-side state saving. The browser merely sends an identifier for looking up the component tree in the session. Without the session, there is no component tree. When that happens, the only reasonable thing for JSF to do is to throw and ViewExpiredException or simply render the page without processing any events.

...that is, until Facelets came along. Facelets offers a solution to the stale form problem. By setting the facelets.BUILD_BEFORE_RESTORE context-param to true, you can have Facelets reconstruct the component tree in the RESTORE VIEW phase if the one in the session has expired (or the session as a whole has expired). This would seem to match the behavior of client-side state saving. It does not. The reason is that the contract of requiring an existing view state to exist is now absent and a postback is no different than a GET-requested page action. A form on a plain HTML page can now submit a POST request directly to a JSF application (you do need an empty javax.faces.ViewState hidden field for it to work).

If you are enabling this global Facelets option, your application is wide open to CSRF attacks.

To make things even more troubling, Facelets has become the standard view handler in JSF 2.0 and this behavior of Facelets is now the default. So what do we do?

Well, as it turns out, because of a recent specification change, the build before restore feature has been inadvertently disabled in Mojarra (the JSF reference implementation), so we may not have to worry about it after all in JSF 2.0. See Mojarra Issue 1028.

A built-on demand of the component tree on the server is only secure if we can ensure that the request is coming from a valid source. Hence, the Seam <s:token> support should always be used if you enable build before restore.

Considering page actions

Seam allows actions to be invoked when a page is requested using a feature known as page actions. A page action is a method expression that is tied to a JSF view URI and is invoked before the RENDER RESPONSE phase on an NonFaces (GET) request. A page action in Seam is defined in the pages.xml descriptor:

<page view-id="/myPage.xhtml">
    <action execute="#{myBean.myAction}"/>
</page>

When a GET request is processed for the URL http://example.com/myPage.seam, the action will be executed. Page actions are both a blessing and a curse. They are a better substitutes for filters since they can be associated with a view URI regardless of the request life cycle leading up to it being requested (bookmarkable). They can also be tied into the declarative JSF navigation system. However, their downfall is that they can be easily abused. Unlike a JSF POSTback, they do not require a page to have been viewed previously (i.e., an established javax.faces.ViewState). A page action is an CSRF attack vector if:

  • The action only validates permissions of the current user based on the users HTTP session. A session cookie will be automatically transmitted by the browser and authorization will be granted.
  • The action is not idempotent, that is, if it modifies resource state such as writing to the database. If the action only retrieves data from the database and doesn't have any persistent or potentially dangerous side effects, an attacker might execute the request but it won't do any harm.

In general, GET requests should not trigger unsafe actions. It's reasonable to use a page action to preload data but it's inappropriate to use a page action for operations that manipulate sensitive or secure data, such as transfer money between accounts. Whenever sensitive data is involved, it's important to provide a splash page that confirms the action and the user's intent to perform it.

A GET request should be idempotent and repeatable and it should not make changes to persistent (database) state. If you absolutely can not avoid assigning an unsafe action to a view URI, you need to add an additional random token as a parameter and validate it against the value stored in the users session. This is effectively a secondary session identifier which is, unlike the session cookie, not transmitted automatically by the browser and hence needs to be obtained by an attacker to forge a valid malicious request.

Seam itself does not offer you any tools or functions to do this, and you should consider this is a bad practices that is best avoided entirely.

Exposing JavaScript actions through @WebRemote

If you annotate a Seam component method with @WebRemote, Seam will generate a JavaScript interface that you can use to call that method directly from the client. This method call is wrapped in an HTTP POST request. The session identifier cookie will be transmitted automatically to the server.

An attacker can forge a call to any method you annotate with @WebRemote, with the privileges of the currently logged-in user. In general, these methods are required to be safe and idempotent - you should only read and not write data.

Seam Remoting with JavaScript currently offers no automatic protection against CSRF!

Seam's <s:token> protection feature

Seam (starting with 2.1.2RC1) introduces the UI component tag <s:token> to secure JSF form POST requests against CSRF attacks. (NOTE: If the solution below is implemented in JSF 2, then Seam does not need to provide a custom solution and we'll remove <s:token> from Seam. However, we still need to implement something equivalent for Seam Remoting.)

The <s:token> UI component performs validation in two steps:

1. When rendered, it assigns a unique identifier to the browser using a cookie that lives until the end of the browser session. This is roughly the browser's private key. The <s:token> tag is used inside of an <h:form> and generates a hidden form field named javax.faces.FormSignature. The form signature is calculated as follows:

sha1( signature = viewId + "," + formClientId, salt = clientUid )

The developer can also choose to incorporate the session identifier into this hash for a more secure token (at the cost of binding it to the session):

sha1( signature = viewId + "," + formClientId + "," + sessionId, salt = clientUid )

2. When the form is submitted, the hash is recreated on the server and compared against the value of the javax.faces.FormSignature parameter. If validation fails, an org.jboss.seam.ui.UnauthorizedCommandException will be thrown.

When a form with <s:token> is rendered, a JavaScript check is used to validate cookie support in the browser. If cookies are disabled, a warning is presented to the user. This warning will let the user know that the form POSTback will fail if cookies are not enabled.

See the forum discussion thread for usage and caveats.

Further Reading

3 comments:
 
17. Nov 2009, 18:18 America/New_York | Link

The following does not seem to be correct:

----- The value is always:

'j_id' + a sequential number

where the sequential number is the number of views in the user's session (always starting at 1). -----

Instead the sequential number seems to be fixed for all sessions. So the protection against CSRF is almost zero.

 
19. Nov 2009, 15:11 America/New_York | Link

I stand corrected.

 
07. Jun 2010, 03:04 America/New_York | Link

The link to the forum discussion thread should point here.