Following on from CSRF Mitigation Options, here we explore how the CsrfFlowExecutionListener
[1] CSRF mitigation technique can be applied to the IdP's external authentication mechanism, helping to prevent Login CSRF across ‘external’ authentication strategies.
There are of course any number of possible custom external authentication strategies, not all will benefit from or require CSRF mitigation. Broadly speaking, these can be split into four types:
For (1) and (2), as there is no interaction with the user which could be forged by an attacker, simply passing a CSRF token in and out of the server-side authentication strategy provides no security benefit.
For (3), the developer must embed a CSRF token into any HTML forms that are written back to the client. Once POSTed back to the Servlet, the developer must (within that request/response cycle) call finishExternalAuthentication
on the ExternalAuthentication
API. The ExternalAuthenticationImpl
then needs to guarantee the token is present in the redirect back to the IdP.
For (4), A CSRF token is redundant for any interaction with the user that is not the final interaction before the token is sent back to the IdP to be checked. That is unless the developer of the authentication strategy takes responsibility for checking the token during each request.
In implementation, consideration must also be given to situations where CSRF protection has been disabled, and how best to communicate that to the developer.
If CSRF protection is enabled, and external view states are included by default, any external authentication servlet must adhere to the following process:
CsrfFlowExecutionListener
generates a CsrfToken when the ExternalTransfer
view-state is reached.ExternalAuthenticationImpl
before the view is rendered in the view-scope, and placed inside the HTTP request attributes - see Option 1; orCsrfTokenFlowExecutionListener
against that stored in the viewScope.Extend ExternalAuthenticationImpl
to take a CsrfToken
in its constructor (or as a parameter), and inject it during the sub context creation in the view-state (on-render).
<evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).addSubcontext(new net.shibboleth.idp.authn.context.ExternalAuthenticationContext(new net.shibboleth.idp.authn.impl.ExternalAuthenticationImpl(calledAsExtendedFlow\?:false,viewScope.csrfToken)), true).setFlowExecutionUrl(flowExecutionUrl + '&_eventId_proceed=1')" /> |
Set the CsrfToken
as an attribute of the HTTP request when starting authentication e.g. in ExternalAuthenticationImpl#doStart
.
request.setAttribute(CSRF_TOKEN_PARAM, csrfToken); |
The developer of an external authentication mechanism must then extract the token from the HTTP request, e.g.
final CsrfToken tokenFromRequest = (CsrfToken)request.getAttribute(ExternalAuthentication.CSRF_TOKEN_PARAM); |
Notes:
Extend the ExternalAutthentication
API to extract the CsrfToken
from the spring-webflow active session viewScope (using the new external auth API mechanism):
@Nullable public static CsrfToken getCsrfToken(@Nonnull @NotEmpty final String key, @Nonnull final HttpServletRequest request) throws ExternalAuthenticationException { final Object obj = request.getServletContext().getAttribute(SWF_KEY); if (!(obj instanceof FlowExecutorImpl)) { throw new ExternalAuthenticationException("No FlowExecutor available in servlet context"); } try { final FlowExecutionRepository repo = ((FlowExecutorImpl) obj).getExecutionRepository(); ExternalContextHolder.setExternalContext( new ServletExternalContext(request.getServletContext(), request, null)); final FlowExecution execution = repo.getFlowExecution(repo.parseFlowExecutionKey(key)); final MutableAttributeMap<Object> viewScopeMap = execution.getActiveSession().getViewScope(); final Object tokenObject = viewScopeMap.get("csrfToken"); //check csrf is enabled\? if (tokenObject==null || !(tokenObject instanceof CsrfToken)) { return null; } return (CsrfToken)tokenObject; } catch (final FlowExecutionRepositoryException | IllegalStateException e) { throw new ExternalAuthenticationException("Error retrieving flow conversation or view scope", e); } finally { ExternalContextHolder.setExternalContext(null); } } |
The developer of an external authentication mechanism must then retrieve the token from the ExternalAuthentication
API, e.g.
final String key = ExternalAuthentication.startExternalAuthentication(request); final CsrfToken token = ExternalAuthentication.getCsrfToken(key, request); |
Embedding the CSRF Token in a suitable HTML response
To work as protection for a CSRF attack, the CSRF token parameter name and value must be embedded in HTTP forms sent in a response to the user-agent. For example, as a simple example in JSP:
<%@ page pageEncoding="UTF-8" %> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %> <%@ page import="net.shibboleth.idp.authn.ExternalAuthentication" %> <%@ page import="net.shibboleth.idp.authn.ExternalAuthenticationException" %> <%@ page import="net.shibboleth.utilities.java.support.net.CsrfToken" %> <% CsrfToken csrfToken = null; String redirect = null; try { final String key = ExternalAuthentication.startExternalAuthentication(request); csrfToken = ExternalAuthentication.getCsrfToken(key,request); redirect = ExternalAuthentication.getExternalRedirect("/idp/external.jsp",key); } catch (final ExternalAuthenticationException e) { throw new ServletException("Error processing external authentication request", e); } %> <form action="<%=redirect%>" method="post"> <input name="username" id="username"> <input type="hidden" name="<%=csrfToken.getParameterName()%>" value="<%=csrfToken.getToken()%>" id="csrf_token"> <input type="submit" value="Login"> </form> |
External authentication servlets are ‘redirected’ back into the IdP external-auth webflow. The token is required in the GET request, and hence must be added as a query parameter to the URL e.g. as a very simple example, inside the ExternalAuthenticationImpl#doFinish
method:
response.sendRedirect(extContext.getFlowExecutionUrl()+"&csrf_token="+request.getParameter("csrf_token")); |
---
[1] The CsrfFlowExecutionListener
provides a core cross-cutting security concern across IdP view states by injecting and validation CSRF tokens.