Enterprise grade Java.
You'll read about Conferences, Java User Groups, Java, Integration, Reactive, Microservices and other technologies.

Monday, June 6, 2011

Binding SSL-Sessions to HttpSessions in GlassFish

10:40 Monday, June 6, 2011 Posted by Unknown 8 comments:
, , ,
You might have noticed, that I am working my way through the security principles regarding secure web applications at the moment. The main idea about this is to enable GlassFish to deliver high secure applications. One of the things making my brain hurt a bit is the Session Hijacking attack. It consists of the exploitation of the http session control mechanism, which is normally managed for a session token.
In our case the JSESSIONID. The Session Hijacking attack compromises the session token by stealing or predicting a valid session token to gain unauthorized access to the applications running on you GlassFish.
There are different type of possible attacks. See the OWASP page about that for details. If you are going to address this topic you have different options from an implementation perspective. This is what I am going to describe in this blog post.

SSL is your friend
The basic requirement for session hijack prevention is to use https for your applications. This primary assures, that it's not easy to a) sniff the session out of the http stream and prevents simple man-in-the-middle attacks. Running in very high secure environments requires installing ssl certificates to your GlassFish and running all http-listeners in secure mode.

Where to put and how to configure the JSESSIONID?
Before you start with further reading, you should be aware that the whole topic is about the Servlet spec and about containers. The spec itself requires the session tracking cookie to be most convenient for any user and defines many ways of storing and transmitting it. This is a behavior that is undesired in high secure environments. So the first thing is to restrict the session tracking cookie to the minimum needed.

<session-config>
<!--
Specifies General Session Timeout in Minutes
-->
<session-timeout>15</session-timeout>
<cookie-config>
<!--
Specifies whether any session tracking cookies created
by this web application will be marked as HttpOnly
-->
<http-only>true</http-only>
<!--
Specifies whether any session tracking cookies created
by this web application will be marked as secure
-->
<secure>true</secure>
</cookie-config>
<!--
Specifies whether JSESSIONID is added to the URL
-->
<tracking-mode>COOKIE</tracking-mode>
</session-config>

What is also true is, that the spec would allow for "secure" HttpSession identifiers. In 7.1.2 it states, that
Secure Sockets Layer, the encryption technology used in the HTTPS protocol, has a
built-in mechanism allowing multiple requests from a client to be unambiguously
identified as being part of a session. A servlet container can easily use this data to
define a session.
Do my knowledge GlassFish does not implement this features up to now. So you have to work around this. Let's go:

HttpSession ID and SSL Session ID
Even if you are running SSL with the strongest certificates available, don't use URL-Rewriting and have httpOnly and secure enabled, nobody prevents you from man-in-the-browser attacks or client-side attacks. So, there are still some possibilities to gather the Session ID and use it from a different computer. If you are willing to implement some protection here, you are in need of some additional logic in your application which binds the SSL ID to your HttpSession ID.

SessionIdValve
The most obvious thing is to simply make both of them equal. The basic idea here is to take the SSL Session ID from the request and implement your own SessionIdValve which instantiates a HttpSession with that ID. Jan Luehe has a basic example how to achieve this with a GlassFish v2 on his blog. The only thing to do is to not take the client IP but the coyoReq.getAttribute("javax.servlet.request.ssl_session") and put it as HttpSession back to the request (see this forum discussion for more details). To be honest, I was not able to get this working with GlassFish 3 (see here). Don't worry: I don't like this solution anyway because it's simply not portable enough. You tie your logic very closely to the container you run in and so, you should avoid this approach in general.

Session Attributes
What I like a bit more is to use something like a HijackingPreventionFilter. This could be a simple @WebFilter that is mapped to any resource that should be protected

@WebFilter(dispatcherTypes = {DispatcherType.REQUEST, DispatcherType.FORWARD,
DispatcherType.INCLUDE, DispatcherType.ERROR}, urlPatterns = {"/*"})

On the first request it checks for an existing session and either does

chain.doFilter(request, response);

or checks some session attributes against the information in the actual request. The only prerequisite here is, that you have something in place to add the initial information to your newly created session. There are some places you could come up with. The best would probably be your login. Due to security reasons you should always _renew_ the HttpSession after a successful login. Afterwards you could assume that the request is from the client authenticating against your system. Just get the SSL Session ID and set it as HttpSession attribute there:

String cidSize = (String)request.getAttribute("javax.servlet.request.key_size");
String cid = (String)request.getAttribute("javax.servlet.request.ssl_session");
...
session.setAttribute("CLIENT_SSL_ID", cid);

You noticed the cidSize attribute? The javax.servlet.request.ssl_session is not an official servlet supported attribute. Grizzly set's it, when the webcontainer asks to set ALL ssl attributes. So when you just ask for "javax.servlet.request.ssl_session", the webcontainer doesn't recognize it as known SSL attribute and nothing happens (Null), but when you first ask for the key size, it's getting recognized by the webcontainer and it asks Grizzly to set all known SSL attributes including the ssl_session.
Another good place could be an HttpSession listener. The big problem here still is, that you are programming against container features which prevent your application from being portable.

Custom HTTP Header variables
What really resolves the mess is, if you have any networking device or proxy in front of your GlassFish that simply puts the ssl-session-id as a custom header variable to your request. In this case you don't even have to care for it yourself, you simply change the code in your webfilter to check for your request headers.

String cid = httpRequest.getHeader("HEADER_CLIENT_SSL_ID");

The only drawback here is, that you basically lose the chance to locally run it without the proxy. So you need to put a startup class in place which adds your filter to the configuration if you are in production mode.

Conclusion: Security is painful
The higher your security requirements are, the more painful your development gets. That's the basic message. You don't have a single switch to turn on to secure your application but you have a lot of screws to tighten to get everything right. This post only shows a little bit from the complete story. What I would like to see is that the Servlet EG is taking some action defining more basic security into the spec.
What's also true is, that nobody should runs a high secure GlassFish without any kind of Enterprise Access Management (EAM) solution in place. Those typically address the described issues with their own plugins and tokens. Anyway, there are still some smaller installations out there suffering from the very little capabilities of todays Java EE servers.
Comments and suggestions? I would love to read them!

8 comments:

  1. Markus,

    In your last code example with the customer HTTP header, I think you meant to make this the cid and not the cidSize correct?

    String cidSize = httpRequest.getHeader("HEADER_CLIENT_SSL_ID");

    Also, I'm a bit unfamiliar with this topic, is the basic logic that once the CID and SessionID are correlated, that a different CID should not be allowed to access this session?

    ReplyDelete
  2. James,

    thanks, that was a c&p error :) And yes, you are right, the idea here is to have some logic to prevent other CIDs from accessing the same session.

    M

    ReplyDelete
  3. Hi Markus,

    Nice idea, and well put forward.

    Unfortunately, it won't stop a MITM attack that maintains the SSL SessionID properly. I am the author of WebScarab, a fairly well-known intercepting proxy.

    While I'm not sure that it will just work out of the box against a server configured as you describe, I'm pretty sure that it would not be terribly difficult to fix it so that it does maintain the SSL SessionID from one request to the next, allowing the use of a MITM proxy again.

    That said, I think it is a good technique, and would probably be quite effective at stumping many attackers trying to test your session management implementation.

    I'd be curious to see what happens in some boundary cases, though. For example, if the browser is left idle for longer than the idle timeout, does the session get regenerated using the same SSL SessionID value if it then makes a new request? Would this be then a completely new session object?

    ReplyDelete
  4. Hi Roger,

    thanks for the comment. You need some additional checks to completely prevent MITM. It's a good idea in general to add more client information to your checks (e.g. Browser version, IP) and in general check against a hash of your information base.
    Regarding the boundary cases: This best works, if you have some reliable session management in place (combined with programmatic container authentication). The filter itself should only depend on an existing, pre-configured session. If it is not there or the information base is incomplete it only should request.logout() and dispatch to the applications login page.
    At the moment I am not able to disclose the source of my implementation. Maybe I get the chance to rework a simple sample and put it up, too.

    Thanks for your comment,
    Markus

    ReplyDelete
  5. About the 'javax.servlet.request.ssl_session' not being standard / always set, check out the specs:

    3.8 SSL Attributes If a request has been transmitted over a secure protocol, such as HTTPS, this information must be exposed via the isSecure method of the ServletRequest interface. The Web container must expose the following attributes to the servlet programmer:
    TABLE 3-3 Protocol Attributes
    Attribute | Attribute Name | Java Type
    cipher suite | javax.servlet.request.cipher_suite | String
    bit size of the algorithm| javax.servlet.request.key_size | Integer
    SSL session id | javax.servlet.request.ssl_session_id | String
    If there is an SSL certificate associated with the request, it must be exposed (...)

    So, seems you are using some GF specific attribute name, whereas there is another, standard option. Or is it what you wanted to say, and GF doesn't play along?

    ReplyDelete
  6. Hi Markus,

    What I'm getting at is that it is not possible to eliminate MITM completely. By using non-standard or unusual techniques, you can make it more difficult, and necessitate some code changes, perhaps, but even adding the IP address and Browser version will NOT prevent MITM.

    For example, the IP address that you use will be that of the MITM, not the end browser. The browser user-agent string will just be forwarded by the MITM unchanged, so there is not difference to detect to reveal the presence of the MITM.

    The only guaranteed way to eliminate non-voluntary MITM (i.e. the user is not aware of the MITM) is to use client certs. If the user is actively aiding the MITM (e.g. for testing purposes), even that won't work, as they can make the client cert available to the MITM program as well.

    ReplyDelete
  7. Hi Wujek,

    your right. GF seems not to use the official Servlet Spec Attribute. See here:
    http://java.net/jira/browse/GLASSFISH-16818

    Thanks for clarification,
    Markus

    ReplyDelete
  8. Roger,

    right. Completely. It's all about making things very difficult to break. Client Certs are the most secure and reliable method by now.

    Thanks,
    Markus

    ReplyDelete