28

I have a web application which is running on a Tomcat 7 server. The cookie with session id has by default the flags HttpOnly and Secure. I want to disable this flags for the JSESSIONID cookie. But it wont work. I have changed this in my web.xml file but it is not working.

<session-config>
    <session-timeout>20160</session-timeout>
    <cookie-config>
        <http-only>false</http-only>
        <secure>false</secure>
    </cookie-config>
</session-config>

I know this is a security risk because a attacker is able to steal the cookie and hijack the session if he has found a xss vuln.

The JSESSIONID cookie should be send with HTTP and HTTPS and with AJAX requests.

Edit:

I have successfuly disabled the HttpOnly flag by adding the following option to the conf/context.xml file:

<Context useHttpOnly="false">
....
</Context>
dur
  • 13,039
  • 20
  • 66
  • 96
JEE-Dev
  • 281
  • 1
  • 3
  • 5

3 Answers3

2

I did not find a solution in Tomcat to this but if you're using apache as a reverse proxy you can do:

Header edit* Set-Cookie "(JSESSIONID=.*)(; Secure)" "$1"

with mod_headers which will munge the header on the way back out to remove the secure flag. Not pretty but works if this is critical.

George Powell
  • 7,511
  • 8
  • 30
  • 43
2

If you read the code from tomcat you will find:

// Always set secure if the request is secure
if (scc.isSecure() || secure) {
    cookie.setSecure(true);
}

So trying to deactivate Secure flag on JSESSIONID cookie with sessionCookieConfig.setSecure(false); in a listener or <cookie-config><secure>false</secure></cookie-config> in the web.xml WON'T WORK as Tomcat force the secure flag to true if the request is secure (ie came from an https url or the SSL port).

A solution is to use a request filter to modify the JSESSIONID cookie on the server response immediately after the session creation. This is my implementation (very basic):

public class DisableSecureCookieFilter implements javax.servlet.Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
            request = new ForceUnsecureSessionCookieRequestWrapper((HttpServletRequest) request, (HttpServletResponse) response);
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() { }

    public static class ForceUnsecureSessionCookieRequestWrapper extends HttpServletRequestWrapper {
        HttpServletResponse response;

        public ForceUnsecureSessionCookieRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
            super(request);
            this.response = response;
        }

        @Override
        public HttpSession getSession(boolean create) {
            if(create) {
                HttpSession session = super.getSession(create);
                updateCookie(response.getHeaders("Set-Cookie"));
                return session;
            }
            return super.getSession(create);
        }

        @Override
        public HttpSession getSession() {
            HttpSession session = super.getSession();
            if(session != null) {
                updateCookie(response.getHeaders("Set-Cookie"));
            }

            return session;
        }

        protected void updateCookie(Collection<String> cookiesAfterCreateSession) {
            if(cookiesAfterCreateSession != null && !response.isCommitted()) {
                // search if a cookie JSESSIONID Secure exists
                Optional<String> cookieJSessionId = cookiesAfterCreateSession.stream()
                                                        .filter(cookie -> cookie.startsWith("JSESSIONID") && cookie.contains("Secure"))
                                                        .findAny();
                if(cookieJSessionId.isPresent()) {
                    // remove all Set-Cookie and add the unsecure version of the JSessionId Cookie
                    response.setHeader("Set-Cookie", cookieJSessionId.get().replace("Secure", ""));

                    // re-add all other Cookies
                    cookiesAfterCreateSession.stream()
                            .filter(cookie -> !cookie.startsWith("JSESSIONID"))
                            .forEach(cookie -> response.addHeader("Set-Cookie", cookie));
                }
            }
        }
    }

}

and in the web.xml :

<filter>
    <filter-name>disableSecureCookieFilter</filter-name>
    <filter-class>com.xxxx.security.DisableSecureCookieFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>disableSecureCookieFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Remember that enabling insecure cookies bypass an important https security! (I had to do that for a smooth transition from http to https)

PCO
  • 525
  • 6
  • 11
  • Instead of filtering on prefix and secure anywhere in the line, it's more robust to use e.g. java.net.HttpCookie.parse(str) and then use the getter methods to extract the proper parts. – Stefan L Aug 07 '20 at 13:50
-1

In addition to George Powell's solution above for Apache, if you are on IIS, you can solve it as follows:

  1. Install the IIS URL Rewrite module
  2. Add the following to your web.config

<rewrite>
  <outboundRules>
    <rule name="RemoveSecureJessionID">
      <match serverVariable="RESPONSE_Set-Cookie" pattern="^(.*JSESSIONID.*)Secure;(.*)$" />
      <action type="Rewrite" value="{R:1}{R:2}" />
    </rule>
  </outboundRules>
</rewrite>

This solution was from Pete Freitag's blog

As stated above, since the recent Chrome update (Jan 2017), this has become an issue.

daamsie
  • 1,319
  • 9
  • 14