-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Description
CSRF protection provided by @EnableWebSocketSecurity
is broken. I have identified 2 things that prevent the CsrfChannelInterceptor
from validating the CSRF token from the CONNECT headers.
First problem encounted was the stack trace below. The CsrfChannelInterceptor
was trying to access the CsrfToken
when processing the CONNECT message. The changes to made in 475b3bb and d94677f to defer the loading of the token caused the deferred supplier to try to access the http upgrade HttpServletRequest
after it has gone out of scope.
Attempting to come up with a fix for the first problem I added an additional HandshakeInterceptor that forced eager loading the CsrfToken while they upgrade HttpServeletRequest was still in scope. I added the source below for clarity.
This overcame the first problem but then the CsrfChannelInterceptor
started throwing InvalidCsrfTokenException
. After investigating it became apparent the CsrfChannelInterceptor
was only design to work with tokens that were not masked. Since XorCsrfTokenRequestAttributeHandler
is now the default implementation for CsrfTokenRequestHandler
the CsrfChannelInterceptor
needs to be modified to have the same or similar logic as the CsrfFilter#doFilterInternal
.
stack_trace: org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:149)
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:125)
at org.springframework.web.socket.messaging.StompSubProtocolHandler.handleMessageFromClient(StompSubProtocolHandler.java:310)
at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:335)
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:113)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:84)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:129)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:162)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:157)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalStateException: The request object has been recycled and is no longer associated with this facade
at org.apache.catalina.connector.RequestFacade.getAttribute(RequestFacade.java:279)
at jakarta.servlet.ServletRequestWrapper.getAttribute(ServletRequestWrapper.java:85)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getCurrentSession(SessionRepositoryFilter.java:238)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:281)
at org.springframework.session.web.http.SessionRepositoryFilter$SessionRepositoryRequestWrapper.getSession(SessionRepositoryFilter.java:194)
at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:244)
at jakarta.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:244)
at org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.loadToken(HttpSessionCsrfTokenRepository.java:65)
at org.springframework.security.web.csrf.RepositoryDeferredCsrfToken.init(RepositoryDeferredCsrfToken.java:63)
at org.springframework.security.web.csrf.RepositoryDeferredCsrfToken.get(RepositoryDeferredCsrfToken.java:48)
at org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler.lambda$deferCsrfTokenUpdate$0(XorCsrfTokenRequestAttributeHandler.java:63)
at org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler$CachedCsrfTokenSupplier.get(XorCsrfTokenRequestAttributeHandler.java:139)
at org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler$CachedCsrfTokenSupplier.get(XorCsrfTokenRequestAttributeHandler.java:126)
at org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken.getDelegate(CsrfTokenRequestAttributeHandler.java:89)
at org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler$SupplierCsrfToken.getHeaderName(CsrfTokenRequestAttributeHandler.java:75)
at org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor.preSend(CsrfChannelInterceptor.java:56)
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:181)
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:135)
... 30 common frames omitted
public class CsrfTokenHandshakeInterceptorFix implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();
CsrfToken token = (CsrfToken) httpRequest.getAttribute(CsrfToken.class.getName());
if (token == null) {
return true;
}
// Eagerly resolve token values
token = new DefaultCsrfToken(token.getHeaderName(), token.getParameterName(), token.getToken());
attributes.put(CsrfToken.class.getName(), token);
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
})