Spring Security Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack
Asked Answered
S

4

15

i have a GWT application using Spring Security3.1.2 running in a tomcat 7. i am using UsernamePasswordAuthenticationFilter and PersistentTokenBasedRememberMeServices to persists logins on the DB. moreover, i am using tomcat PersistentManager to save session in DB as well. now my problem is that every time i try to login i get Invalid remember-me token (Series/token) mismatch CookieTheftException (i added the stack below). i tried deleting the session from tomcat_sessions table as follows

  1. shutdown tomcat
  2. delete records from tomcat_sessions table
  3. start tomcat
  4. try loging in to the application where i get the CookieTheftException again...

i also noticed that even after deleting all records in tomcat_sessions table and when i restart tomcat, tomcat_sessions gets filled up with all the session i deleted earlier...

i also deleted all records in Spring persistent_logins table and disabled tomcat PersistentManager but still having the same problem...

any idea what might be the problem? thank you

SEVERE: Servlet.service() for servlet [springMvcServlet] in context with path [/brate] threw exception
org.springframework.security.web.authentication.rememberme.CookieTheftException: Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack.
    at org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices.processAutoLoginCookie(PersistentTokenBasedRememberMeServices.java:102)
    at org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices.autoLogin(AbstractRememberMeServices.java:115)
    at org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter.doFilter(RememberMeAuthenticationFilter.java:97)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:183)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at com.brate.admin.server.servlet.crawler.GoogleBotFilter.doFilter(GoogleBotFilter.java:202)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:936)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1004)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:695)
Suckling answered 18/11, 2013 at 16:46 Comment(0)
R
36

Just so that we are on the same page I will first take a minute to explain how I understand this persistent token mechanism to work.

Starting from scratch (no entries in the persistent_logins table):

On login success: A persistent token will be created for the user with some random hash. A cookie is created for the user with the token details on it. A session is created for the user.

As long as the user still has an active session then no remember me functionality will be invoked upon authentication.

After the user's session has expired: The remember me functionality kicks in and uses the cookie to fetch the persistent token from the database. If the persisted token matches the one from the cookie then everyone is happy as the user gets authenticated, a new random hash is generated and the persistent token is updated with it and the user's cookie is updated as well for subsequent requests.

But if the token from the cookie doesn't match that of the persisted token then you get a CookieTheftException. The most common reason for the tokens not to match is that 2 or more request were fired off in quick succession, where the first request will get through, generating the new hash for following requests, but the second request will still have the old token on it and thus results in the exception.

To largely avoid the CookieTheftException, make sure that requests to your webapp's content (such as images, fonts, scripts etc.) don't go through Springs authentication filters. To do this simply add another <http> config above your normal security configuration and specify that you don't want any security for requests to you resources (use your relevant path instead of /resources/**):

<http pattern="/resources/**" security="none"/>
<http ... (normal config) ... 

(For Java Config see here: How do I define http "security = 'none' in JavaConfig?)

If you delete an user's token from the database (and their session has expired), then the user will get logged out upon the next request. So what you are saying about your persistent tokens (in the persistent_logins table) automatically getting recreated makes very little sense and I highly doubt that is the case. The PersistentTokenRepository's createNewToken(PersistentRememberMeToken token) method only get's called on login success.

Lastly if you're still getting the exception it helps to attach the sources for the PersistentTokenBasedRememberMeServices and to put a break point in the processAutoLoginCookie method to see which request is causing the CookieTheftException.

I hope this helps.

Rightly answered 18/11, 2013 at 19:16 Comment(10)
Thanks Markus, i understand all you said regarding the authentication/token dance. i will debug the PersistentTokenBasedRememberMeServices class for more details. but what is confusing me is that all works well when i run the application in GWT Dev mode i.e from GWT jetty. the problem is when i compile and run the application in tomcat. would tomcat PersistentManager cause such a problem or it has nothing to do with spring session management?Suckling
regarding the automatic creation of sessions, it is happening with tomcat PersistentManager tomcat_sessions table not Spring persistent_logins table.Suckling
ok here is what i did, i disabled tomcat PersistentManager by replacing context.xml and catalina.properties with fresh ones and suddenly all worked well... did you try running remember-me service with PersistentManager before?Suckling
Hi Sameeth, ok right excuse my mix up between the two tables. I haven't used this persistent sessions functionality from Tomcat, but I think what could have happened with your original cookieTheftExceptions is that the existing tokens in persistent_logins would authenticate the user (recreating the session) upon the first request (and thus persist to tomcat_session), but the second request that came in right after still had the old token - causing the exception. Did you previously delete everything from both tables (persistent_logins and tomcat_session)? If so then I'm a little stumped :/Rightly
i tried deleting tomcat_session as described in the original post but tomcat keep filling up the table between server restarts. on the other hand, i was able to delete persistent_logins rows. maybe this caused an unsynchronized session/token mapping between tomcat and Spring. anyway i dropped tomcat PersistentManager for now... :| thank for your followup :). i will post my findings when i work on this again.Suckling
i posted the thread in Spring forum forum.spring.io/forum/spring-projects/security/…Suckling
I was having the same issue until I completely cleared all my browsers data (cache, cookies, etc...) and cleared the persistent_logins entries. I think, at least on my project, I was changing some of the settings and that's what was causing the issues. I haven't had the problem happen again since.Archilochus
@Markus: We are facing a similar issue, As you suggested , even after we ensure that: "To largely avoid the CookieTheftException, make sure that requests to your webapp's content (such as images, fonts, scripts etc.) don't go through Springs authentication filters." We still face the issue as in our case 2/more rest calls are fired in quick succession and uses the same cookie value. Even in the response header of the first rest calls, SET COOKIE is expected to update the rememberMe cookie value. But the second call is fired before that. Any Suggestions. -HemantFigurine
"If the persisted token matches the one from the cookie then everyone is happy as the user gets authenticated, a new random hash is generated" - great tip. I was spending some hours trying to figure out why remember-me were not working. I've already checked Set-Cookie header for a new remember-me token, and they were very similar, but they were NOT EQUAL. So if you are banging your head due to supposedly equal remember-me token but your re-authentication is not working, maybe they are similar but not equal, check them again.Creation
I'd like to share my example of HttpSecurity JavaConfig inspired by the MarkusCoetzee's answer https://mcmap.net/q/822257/-spring-security-remember-me-fails-with-cookietheftexception-duplicateRoentgenotherapy
F
2

I had the same error and notice that it was trying to auto login every request where the security chain was being ignored. You can see which ones by doing

public void configure(WebSecurity web) throws Exception {
    web
        .debug(true)
        .ignoring()
        .antMatchers("/css/**", "/js/**", "/img/**");
}

After this I notice js files and css files where skipping the security chain, I removed those mappings and remember me started working as it should.

public void configure(WebSecurity web) throws Exception {
    web
        .debug(true)
        .ignoring()
        .antMatchers("/img/**");
}
Fulmar answered 11/4, 2014 at 19:54 Comment(0)
C
1

The missing part for my configurations was RememberMeAuthenticationProvider. (http://docs.spring.io/spring-security/site/docs/3.2.2.RELEASE/reference/htmlsingle/#remember-me-impls)

Please note the package for RememberMeAuthenticationProvider has changed and it's not the same as in the docs.

Don't forget to define the same key for PersistentTokenBasedRememberMeServices and RememberMeAuthenticationProvider

This is my configuration:

<s:http auto-config="false"
            use-expressions="true"
            create-session="ifRequired">
        <s:remember-me services-ref="rememberMeServices"
                       authentication-success-handler-ref="rememberMeAuthenticationSuccessHandler"/>
...
</s:http>

 <bean id="rememberMeServices"
          class="org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices">
     <constructor-arg index="0" value="${remember.me.key}" />
...
</bean>

<bean id="rememberMeAuthenticationProvider" class=
        "org.springframework.security.authentication.RememberMeAuthenticationProvider">
        <property name="key" value="${remember.me.key}"/>
    </bean>

<s:authentication-manager alias="authenticationManager">
        <s:authentication-provider ref="rememberMeAuthenticationProvider" />
...
    </s:authentication-manager>

Markus Coetzee answer really cleared things up for me. Thanks!

Cirillo answered 12/3, 2014 at 8:0 Comment(0)
S
0

Fixed this problem with token history - got the idea from this comment

  1. Create table with an ability to store multiple tokens per series
  2. Create repository (JOOQ)
  3. Implement remember me service to check multiple tokens instead of last one

To simulate PersistentTokenBasedRememberMeServices original behaviour you can add .limit(1) to CustomPersistentTokenRepository.findAllBySeries method

Swish answered 10/2, 2023 at 20:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.