...
Option 2 - FlowExecutionListener Injected ViewScoped CSRF Token
Overview
Similar to option 1, but inserting and validating anti-CSRF tokens using a SWF FlowExecutionListener. In summary, using a listener to add a CSRF token to the viewScope on `#viewRendering(..)` and check it against that returned in the HTTP request on `#eventSignaled(..)`.
Advantages of Approach:
- system wide enforcement of a synchroniser token based CSRF defence for SWF view-states.
- For internal views and any custom views created as part of IdP flow extensions.
- View-states can be excluded by configuration.
- CSRF defence could easily be controlled (turned on/off) by a property in
idp.properties
- not implemented. - can, if configured correctly (e.g. catching invalid token exceptions in the flow), be used to show the default (or other) IdP error page with a custom message. Otherwise the generic uncaught exception message will be shown.
- CSRF tokens are refreshed per view render. Arguably slightly stronger than, for example, per-session tokens, as they could be used along with the session ID in a session fixation type attack.
Disadvantages of Approach:
- is tightly coupled to SWF and the lifecycle of a flow.
- does not fit very well with the general IdP architecture:
- is, mostly, hidden from flow definitions.
- can only signal error states i.e. an invalid CSRF token, by throwing RuntimeExceptions or subclasses thereof e.g.
InvalidCsrfTokenException
. Whereas nearly all other error states in the IdP are encoded directly as SWF Events.- Although these can be caught and turned into an error-state by SWF transitions.
- the listener will be called for every SWF lifecycle event, which may incur a small performance penalty.
- this can be limited if required by specifying which flows to observe as criteria in the configuration.
- only checks CSRF token validity on ‘proceed’ events from view-states, although this can be configured by a condition Predicate. It is possible therefore to bypass this e.g. if an event ID other than ‘proceed’ was used to transition out of a view-state. As this is possible, it needs checking.
- In addition, how this interacts with external servlets and MFA flows needs further investigation.
Implementation
The proof of concept can be found on my personal git repository [git@git.shibboleth.net:philsmart/java-identity-provider] (branch feature/anti-xsrf-token-flowext-listener
).
New Classes
- A
CsrfToken
API interface . This defines token implementations that return token values and HTTP parameter names.- packaged inside
idp-session-api
. Although this maybe better inside ofjava-support, or idp-ui
?
- packaged inside
- A default implementation of CsrfToken, namely
SimpleCsrfToken
. Encapsulating both token value and HTTP parameter name as a non-empty Strings. Has no business logic.- packaged inside
idp-session-api
. Although this maybe better inside ofjava-support, or idp-ui
?
- packaged inside
- A
CsrfTokenManager
to provide helper methods for generating and validating CSRF tokens .- packaged inside
idp-session-impl
. Although this maybe better inside ofjava-support, or idp-ui
? - uses a configurable
SecureRandomIdentifierGenerationStrategy
to generate anti-csrf tokens - even though suitable, is generating a token and not an identifier.. - currently fixed to a single implementation.
- packaged inside
- A
CsrfTokenFlowExecutionListener
which reacts to SWF lifecycle events.- adds CSRF tokens to the viewScope when views are rendering.
- checks the CSRF token in the request matches that stored in the view scope when a ‘proceed’ event occurs.
- Can be configured to ignore or exclude certain view-states by ID.
- An
InvalidCsrfTokenException
, subtype ofFlowExecutionException
, thrown when an invalid CSRF token is found.
Global config changes
The CsrfTokenManager is added to the set of global system beans (global-system.xml):
Code Block |
---|
<!-- Cross Site Request Forgery token manager -->
<bean id="shibboleth.CsrfTokenManager" class="net.shibboleth.idp.session.impl.CsrfTokenManager"
p:csrfParameterName="%{idp.csrf.token.parameter:csrf_token}"/> |
The flow listener is added to the flowExecutor
in the webflow-config.xml
:
Code Block |
---|
<webflow:flow-executor id="flowExecutor">
<webflow:flow-execution-repository max-execution-snapshots="0" conversation-manager="conversationManager" />
<webflow:flow-execution-listeners>
<webflow:listener ref="profileRequestContextFlowExecutionListener"
criteria="%{idp.profile.exposeProfileRequestContextInServletRequest:*}" />
<webflow:listener ref="csrfTokenFlowExecutionListener"/> <!--NEW-->
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="csrfTokenFlowExecutionListener"
class="net.shibboleth.idp.session.impl.CsrfTokenFlowExecutionListener" p:csrfTokenManager-ref="shibboleth.CsrfTokenManager">
<property name="excludedViewstateIds">
<list>
<value>LocalStorageRead</value>
<value>LocalStorageWrite</value>
</list>
</property>
</bean> |
To signify an InvalidCsrfToken
event is ‘local’, add it to the shibboleth.LocalEventMap
in errors.xml
Code Block |
---|
<entry key="InvalidCsrfToken" value="false" /> |
Changes To Flows
To cleanly handle the InvalidCsrfTokenException
the CsrfFlowExecutionListener
throws, a global, on-exception transition must be registered in appropriate flows - in addition to a corresponding end-state
. For example, in the authn-abstract-flow.xml
:
Code Block |
---|
<end-state id="InvalidCsrfToken" />
<global-transitions>
<transition on-exception="net.shibboleth.idp.session.InvalidCsrfTokenException" to="InvalidCsrfToken" />
<transition on="InvalidCsrfToken" to="InvalidCsrfToken" />
... |
Note, if you do not do this, a generic uncaught exception error will be shown.
View Changes
Any HTML Forms must include a hidden input field with both the anti-csrf token ( value
attribute), and the HTTP parameter name (name
attribute).
Code Block |
---|
<form action="$flowExecutionUrl" method="post">
<input type="hidden" name="${csrfToken.parameterName}" value="${csrfToken.token}" id="csrf_token">
...
</form> |
This will then be rendered, for example, in the output as:
Error View Message Customisation
The default error view can then be customised to show a more appropriate error message on an InvalidCsrfToken event. For example, in system/messages/messages.properties
:
...
...
Implementation of this has moved to here.
Option 3 - Add a CSRF token to a new CSRFUI context
Overview
Add a cryptographically secure anti-csrf token to a CSRF subcontext of the ProfileRequestContext (initialised early in the authentication flow at present). The returned CSRF token from a view-state in the HTTP request is checked against that stored in the context by a suitable profile action. The conversation ends and a suitable error message is displayed if an invalid CSRF token is found.
...
- per conversation token is marginally less safe than per view - although this does not seem significant for the IdP, as it has a limited number of view-states, and limited conversation durations.
- initialisation and validation actions would need to be configured/integrated slightly differently for different flows e.g.the admin flow .
- has to be incorporated, where needed, into each flow with view-state.
- not clear at this stage where to place the CSRF validation action. In its own action-state, or inside another etc.
- no guarantees this approach will be adhered too if custom flows with custom views are created.
- small overhead of creating/managing a context and holding onto state.
- Although a single token can safely be used for a number of view-states if required.
Implementation
The proof of concept can be found on my personal git repository [git@git.shibboleth.net:philsmart/java-identity-provider] (branch feature/anti-xsrf-token-context
).
New Classes
All new classes have been packaged inside the idp-ui
module.
- A
CsrfToken
interface. This defines token implementations that return token values and HTTP parameter names. - A default implementation of
CsrfToken
, namelySimpleCsrfToken
. Encapsulating both token value and HTTP parameter name as a non-empty String. Has no business logic. - A new IdP context for holding per conversation CSRF tokens,
CsrfUIContext
. - An action,
InitializeCsrfUIContext
, to initlialise theCsrfUIContext
, generate aCsrfToken
using aSecureRandomIdentifierGenerationStrategy
and configured HTTP Parameter name, and set it onto the context. - A
ValidateCsrfToken
action class. Takes the CsrfToken from the CsrfUIContext and compares it against that extracted from the HTTP request.- is-a AbstractProfileAction, is not an AbstractValidationAction - it does not produce an AuthenticationResult.
- return a new
IdPEventIds.INVALID_CSRF_TOKEN
eventID if token validation fails, else return nothing (null) to allow other actions to proceed. - map that eventID to an end-state and the default error view with custom error message.
Global config changes
Signify an InvalidCsrfToken
event is ‘local’ by adding it to the shibboleth.LocalEventMap
in errors.xml
.
...
Code Block |
---|
<bean id="ValidateCsrfToken" class="net.shibboleth.idp.ui.csrf.impl.ValidateCsrfToken" scope="prototype" p:httpServletRequest-ref="shibboleth.HttpServletRequest"/> |
Changes To Flows
CsrfUIContext Initialisation
...
Importantly, it needs more thought as to which action-state to use, and whether modification to the flow (e.g. new action-state) would be more appropriate.
View Changes
Any HTML Forms must include a hidden input field with both the anti-csrf token ( value
attribute), and the HTTP parameter name (name
attribute).
...
This will then be rendered, for example, in the output as:
Error View Message Customisation
The default error view can then be customised to show a more appropriate error message on an InvalidCSRFToken event. For example, in system/messages/messages.properties
:
...