30

Is there a way to configure Tomcat 7 to create JSESSIONID cookie with a secure flag in all occasions?

Usual configuration results in Tomcat flagging session cookie with secure flag only if connection is made through https. However in my production scenario, Tomcat is behind a reverse proxy/load balancer which handles (and terminates) the https connection and contacts tomcat over http.

Can I somehow force secure flag on session cookie with Tomcat, even though connection is made through plain http?

MrTux
  • 32,350
  • 30
  • 109
  • 146
Krešimir Nesek
  • 5,302
  • 4
  • 29
  • 56

4 Answers4

47

In the end, contrary to my initial tests, web.xml solution worked for me on Tomcat 7.

E.g. I added this snippet to web.xml and it marks session cookie as secure even when reverse proxy contacts tomcat over plain HTTP.

<session-config>
    <cookie-config>
        <http-only>true</http-only>
        <secure>true</secure>
    </cookie-config>
</session-config>
Krešimir Nesek
  • 5,302
  • 4
  • 29
  • 56
8

ServletContext.getSessionCookieConfig().setSecure(true)

Mark Thomas
  • 16,339
  • 1
  • 39
  • 60
2

Another approach, similar to Mark's, would be to use the SessionCookieConfig, but set it in a context listener from JNDI configuration:

The code:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.SessionCookieConfig;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class JndiSessionCookieConfigListener implements ServletContextListener {
    private static final Logger logger = LoggerFactory.getLogger( JndiSessionCookieConfigListener.class );

    private volatile Context jndiSessionCookieConfig;
    private volatile SessionCookieConfig sessionCookieConfig;

    @Override
    public void contextInitialized( ServletContextEvent sce ) {
        String listenerName = getClass().getSimpleName();
        try {
            logger.info( "JNDI override session cookie config found for {}", listenerName );
            jndiSessionCookieConfig = (Context) new InitialContext().lookup(
                    "java:comp/env/" + listenerName );
        }
        catch ( NamingException e ) {
            logger.info( "No JNDI override session cookie config found for {}", listenerName );
        }

        sessionCookieConfig = sce.getServletContext().getSessionCookieConfig();

        String comment = getString( "comment" );
        if ( comment != null ) {
            logger.debug( "\t[comment]: [{}]", comment );
            sessionCookieConfig.setComment( comment );
        }

        String domain = getString( "domain" );
        if ( domain != null ) {
            logger.debug( "\t[domain]: [{}]", domain );
            sessionCookieConfig.setDomain( domain );
        }

        Boolean httpOnly = getBoolean( "http-only" );
        if ( httpOnly == null ) {
            sessionCookieConfig.setHttpOnly( true );
        }
        else {
            logger.debug( "\t[http-only]: [{}]", httpOnly );
            sessionCookieConfig.setHttpOnly( httpOnly );
        }

        Integer maxAge = getInteger( "max-age" );
        if ( maxAge != null ) {
            sessionCookieConfig.setMaxAge( maxAge );
        }

        String name = getString( "name" );
        if ( name != null ) {
            logger.debug( "\t[name]: [{}]", name );
            sessionCookieConfig.setName( name );
        }

        String path = getString( "path" );
        if ( path != null ) {
            logger.debug( "\t[path]: [{}]", path );
            sessionCookieConfig.setPath( path );
        }

        Boolean secure = getBoolean( "secure" );
        if ( secure == null ) {
            sessionCookieConfig.setSecure( true );
        }
        else {
            logger.debug( "\t[secure]: [{}]", secure );
            sessionCookieConfig.setSecure( secure );
        }
    }

    @Override
    public void contextDestroyed( ServletContextEvent sce ) {
    }

    private Boolean getBoolean( String name ) {
        Object value;
        try {
            value = jndiSessionCookieConfig.lookup( name );
            if ( value instanceof Boolean ) {
                return (Boolean)value;
            }
            else {
                return Boolean.valueOf( value.toString() );
            }
        }
        catch ( NamingException e ) {
            return null;
        }
    }

    private Integer getInteger( String name ) {
        Object value;
        try {
            value = jndiSessionCookieConfig.lookup( name );
            if ( value instanceof Integer ) {
                return (Integer)value;
            }
            else {
                return Integer.valueOf( value.toString() );
            }
        }
        catch ( NamingException e ) {
            return null;
        }
    }

    private String getString( String name ) {
        Object value;
        try {
            value = jndiSessionCookieConfig.lookup( name );
            return value.toString();
        }
        catch ( NamingException e ) {
            return null;
        }
    }
}

Inside web.xml:

...
  <listener>
    <listener-class>
      org.mitre.caasd.servlet.init.JndiSessionCookieConfigListener
    </listener-class>
  </listener>
...

In your context.xml:

...
<Environment name="JndiSessionCookieConfigListener/secure"
  type="java.lang.String"
  override="false"
  value="true" />
...

This allows you to set all the session cookie configurations at runtime in the deployment environment. Thus, you could use the same webapp (war file) to do development locally (where you would not have https) and in production where you would ALWAYS want https.

Note, this approach is mentioned in the OWASP documentation

Lucas
  • 14,227
  • 9
  • 74
  • 124
  • 1
    _in production where you would ALWAYS want https._ Not always true. Yes, you want the client connection to always be over HTTPS, but not necessary tomcat. In majority of scenarios tomcat will be behind reverse proxy - and the connection between proxy and tomcat will NOT be HTTPS. – Aleks G Mar 31 '20 at 07:41
0

You could do the following to use secure JSESSIONID cookie over http.

For Tomcat Versions: 7.x/8.x/9.x

Go to Tomcat --> conf folder Open web.xml and add below in session-config section:

<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>

In addition to that, it is recommended to enable SameSite attributes.

Its purpose is to prevent cookies from getting included in cross-site requests in order to mitigate different client-side attacks such as CSRF, XS-Leaks and XSS.

Go to --> conf folder open context.xml Add the following line to the file, below the <Context> tag:

<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor" sameSiteCookies="strict" />

Now restart the tomcat server and in your browser --> DevTools --> Application, you can confirm the changes.

Du-Lacoste
  • 11,530
  • 2
  • 71
  • 51