Work In Progress

Introduction and Context

Here we look at three options for integrating a synchroniser token pattern[1] Cross Site Request Forgery (CSRF) defence into the IdP's Password authentication flow (although it could be configured more widely e.g. other authn views or admin views). 

Login forms can be susceptible to CSRF[2] attacks. In these cases the attacker attempts to trick the victim into logging in as themselves (the attacker). The attacker could then:

  1. Track a victims activity because it is happening in their own session.
  2. Trick the victim into entering sensitive information into an attackers session/account e.g. bank account details etc.

Appendix A describes an example Login CSRF attack on the IdP.

Option 1 - ViewScoped CSRF Token

Overview

Add a cryptographically secure anti-csrf token to the request context viewScope on-entry to any view-state. Then check the returned token (in the HTTP request) matches that stored in the viewScope on a proceed event/transition. If the token is invalid, prevent execution of the transition and re-render the view, else proceed.

Advantages of Approach:

Disadvantages of Approach:

Possible high level implementation issues

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-viewscope).

New Classes

Global config changes

The CsrfTokenManager is added to the set of global system beans (global-system.xml):

 <!-- 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}"/>


Flow beans

The ValidateCsrfToken prototype bean must be declared where it is used (e.g. password-authn-beans.xml, or perhaps more globally when used across flow types)

<bean id="ValidateCsrfToken" class="net.shibboleth.idp.session.impl.ValidateCsrfToken" scope="prototype"
    	p:httpServletRequest-ref="shibboleth.HttpServletRequest"
    	p:csrfTokenManager-ref="shibboleth.CsrfTokenManager"
    	/>


Changes required for web flow view-state

The anti-csrf token is generated on-entry (or on-render) to a view state, and placed inside the SWF viewScope. For example in the DisplayUsernamePasswordPage view-state of the authn-password-flow.xml:

<on-entry>   		
   <evaluate expression="flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.CsrfTokenManager').generateCsrfToken()" result="viewScope.csrfToken" /> 
</on-entry>


Still inside the DisplayUsernamePasswordPage view-state, the anti-csrf token stored in the viewScope must be compared to that returned as a HTTP parameter from the client. This needs to happen (because the token is bound to the viewScope) inside the view-state before the proceed transition is executed and the state exited. Hence, a ValidateCsrfToken action is nested inside the proceed transition:

<transition on="proceed" to="ExtractUsernamePasswordFromFormRequest">   
        	<evaluate expression="ValidateCsrfToken"/>  <!-- CSRF Validation Action -->            	
            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).setAttemptedFlow(thisFlow)" />
</transition>


If token validation fails, SWF will not execute the transition, and the view will be re-rendered with appropriate error message.

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).

 <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:



Option 2 - FlowExecutionListener Injected ViewScoped CSRF Token


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.

Advantages of Approach:

Disadvantages of Approach:

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.


Global config changes

Signify an InvalidCsrfToken event is ‘local’ by adding it to the shibboleth.LocalEventMap in errors.xml.

<entry key="InvalidCSRFToken" value="false" />

Flow beans

The InitializeCsrfUIContext prototype bean is declared in the authn-beans.xml so it can be used to initalise the CsrfUIContext in the AuthenticationSetup action-state of the authn-flow.xml. e.g. in authn-beans.xml


 <bean id="InitializeCsrfUIContext" 
     	class="net.shibboleth.idp.ui.csrf.impl.InitializeCsrfUIContext" scope="prototype"
    	p:csrfParameterName="csrf_token"/>

The ValidateCsrfToken prototype bean must be declared where it is used (e.g. password-authn-beans.xml)

    <bean id="ValidateCsrfToken" 
      	class="net.shibboleth.idp.ui.csrf.impl.ValidateCsrfToken" scope="prototype"
    	p:httpServletRequest-ref="shibboleth.HttpServletRequest"/>


Changes To Flows

CsrfUIContext Initialisation

The InitializeCsrfUIContext action is added to the AuthenticationSetup action-state in the authn-flow.xml.

  <action-state id="AuthenticationSetup">
        <evaluate expression="PopulateAuthenticationContext" />
        <evaluate expression="PopulateSessionContext" />
        <evaluate expression="SetRPUIInformation" />
        <evaluate expression="InitializeCsrfUIContext"/> 
        <evaluate expression="'proceed'" />
        
        <transition on="proceed" to="TestForSession" />
    </action-state>


Error end-state and transition


To correctly transition the IdPEventIds.INVALID_CSRF_TOKEN event ID to an error page, a transition needs to be placed in the action-state where the validation profile action is evaluated (see next section), and a global transition needs to be defined that maps to an end-state.


For example, in the authn-abstract-flow.xml:

<end-state id="InvalidCsrfToken" />

 <global-transitions>
   ...
   <transition on="InvalidCsrfToken" to="InvalidCsrfToken" />
   ...
</global-transitions>
Changes to password authn flow

The CSRF token is taken from the CsrfUIContext and placed inside the SWF viewScope on-render of the DisplayUsernamePasswordPage view-state (or any view-state as required):


<view-state id="DisplayUsernamePasswordPage" view="login">
        <on-render>   	    	
          ...
          <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.ui.csrf.context.CsrfUIContext)).getCsrfToken()" result="viewScope.csrfToken" />
          </on-render>
    ...
</view-state>

Note, as the profile request context is already passed into the view, the CsrfUIContext and token could be extracted by the view templating engine e.g. as is the case with the RelyingPartyContext in the login view.


The CSRF token in the HTTP request is validated against that stored in the CSRF UI context using the ValidateCsrfToken profile action. This action could, as with option 1, be placed inside the ‘proceed’ transition. However, in order to cleanly transition to an end-state and default error view, it has been placed outside the view-state, and inside an action-state - in this case the ExtractUsernamePasswordFromFormRequest action-state.


<action-state id="ExtractUsernamePasswordFromFormRequest">    	
    	<evaluate expression="ValidateCsrfToken" /> 
        <evaluate expression="ExtractUsernamePasswordFromFormRequest" />
        <evaluate expression="'proceed'" />    
    	
    	<transition on="InvalidCSRFToken" to="InvalidCSRFToken" />
        <!-- Let the validate action handle any problems later. -->        
        <transition to="ValidateUsernamePassword" />
</action-state>

An additional transition is required to handle the invalid CSRF token event - this can not be made global in this case, as the *->ValidateUsernamePassword transition would catch it first.

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).

 <form action="$flowExecutionUrl" method="post">            
             <input type="hidden" name="${csrfToken.parameterName}" value="${csrfToken.token}" id="csrf_token">
...

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:

InvalidCSRFToken = invalid-csrf-token
...
invalid-csrf-token.title= Login Failed
invalid-csrf-token.message= CSRF token verification failed.


Other Ideas

Appendix A - Login CSRF Example

Login CSRF Example

The following HTML form is an example of one which can be used by an attacker to post a username and password to the IdP. This is essentially that which is used by the IdP's login view, but pre-populated with a username and password of the attacker - whom must have an active account with the IdP's authentication source. 

<form action="https://localhost:8443/idp/profile/SAML2/Unsolicited/SSO?execution=e1s2" method="post">
<input id="username" name="j_username" type="text"
value="jdoe"/>
<input id="password" name="j_password" type="password" value="PASSWORD"/>
<button type="submit" name="_eventId_proceed">Login</button>
</form>

Such a form could be auto-submitted without the user knowing using JavaScript. 


For the Password IdP flow, the victim using a web browser user agent must:

  1. Initiate a password authentication flow at the IdP using a valid SAML authentication request e.g. attempt a login to a service provider etc.
    1. Note, a victim could be tricked into beginning the authentication process to the IdP the attacker has an account with using some form of wayfless URL e.g. IdP initiated unsolicited SSO or SP Request Initiator.
  2. Receive a response relating to the `login` (DisplayUsernamePasswordPage) spring webflow view-state.
    1. In the process creating a session with the IdP, tracked by a JSESSIONID cookie.
      1. The JSESSIONID cookie is essential for the malicious HTML form post attack to link the victim back to their session and the specific point in the webflow conversation. It is this view-state (keyed by execution and snapshot) which extracts (using the `ExtractUsernamePasswordFromFormRequest` action) the `j_username` and `j_password` POST parameters on the event/transition `proceed` (as referenced by the submit button name).
  3. Not submit their username and password via the login page. Instead, within that same browser session, navigate elsewhere to be tricked into submitting a HTML form (e.g. the one above) which posts an attackers username and password to the correct flow endpoint e.g. idp/profile/SAML2/Redirect/SSO?, using the correct execution and snapshot keys e.g. ?execution=e1s2

Risk (low):

  1. Such an attack is made difficult (in a good way) by the fact the IdP does not have an explicit and static endpoint listening for login requests. Instead the conversation the browser is having with the IdP must be at the correct flow state to accept a HTTP Post request with username and password parameters.
    1. Remembering the CSRF request is blind to the attacker, so if the attacker did start the SSO process for a victim, they would not see the result, and the attacker is not in control of what the victim does next.
  2. The attacker must guess a suitable execution key - although a login would typically happen early in a users conversation with the IdP for a given protocol/binding, and would be fairly predictable.
  3. The attack opportunity only occurs when the user has progressed the password authentication flow to the `DisplayUsernamePasswordPage` view-state and no further. It is fairly unlikely, although not impossible, a user would stop at this point.
  4. The attacker would require an account with the IdP they intended to trick a user into using.

References

1 - https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md#synchronizer-token-pattern

2 - https://seclab.stanford.edu/websec/csrf/csrf.pdf