Cross-Site Scripting And HttpOnly Attribute

Microsoft Internet Explorer(1) has an interesting feature which is not very well known. If a cookie has been set with attribute ‘HttpOnly’ then the browser will forbid any access to it from client-side code. Javascript will not be able to read, write or acknowledge information stored in the cookie.
At first sight this might not seem to be very useful, but if we bring into the picture security of web applications and especially cross-site scripting (XSS)vulnerabilities – things get interesting. One of the classical examples of XSS attack is the one in which a hacker manages to read user’s session identifier from a cookie and use it to access a resource(2).
The most obvious way to remediate that would be to use HttpOnly attribute while setting the JSESSIONID cookie. Unfortunately this step is done by the application server itself and as on now most of them do not use HttpOnly(3). What we might try to do is to rewrite the cookie after it has been created as shown here: http://keepitlocked.net/archive/2007/11/05/java-and-httponly.aspx.
But there’s also another way which you might consider.
We are going to use a Servlet filter to do following:
  1. On the first request within a session we create a token – it should be a random value, just as JSESSIONID is. We store the token as a HttpOnly cookie and also as a session attribute.
  2. On every subsequent request we compare whether the cookie token is equal to the value stored in session.
  3. If cookie token is missing or they are different – we can assume that a third party is trying to impersonate the user by using stolen JSESSIONID.
  4. In such case we can invalidate the whole session, inform an administrator about the attempt or even let the original user know that somebody has been trying to hack in.
Aside of giving us a chance to trace this particular type of abuse, this mechanism can also be used to deal with one more case. Some older servlet containers (that will remain nameless 🙂 ) create session identifiers of a fixed length without providing any configuration option to make them longer. If your application happens to be audited – too short session identifier will most likely by treated as a vulnerability and required to be fixed.
By using the mechanism described above we can effectively control the length of session id, because from practical perspective the session identifier length will be equal to JSESSIONID length plus our hand-generated token length. In real life this approach has helped me to get approval of the ethical hacking team more than once.
A proof-of-concept code below, feel free to suggest changes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;
 
public class HttpOnlyTokenFilter implements Filter {
 
    private static final String TOKEN_KEY = "HTTP_ONLY_TOKEN";
 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
 
        validateToken((HttpServletRequest)request, (HttpServletResponse)response);
        chain.doFilter(request, response);
    }
 
    private void validateToken(HttpServletRequest request, HttpServletResponse response) {
        HttpSession session = request.getSession(true);
        String token = (String)session.getAttribute(TOKEN_KEY);
        if (token == null) {
            token = getRandomString();
            session.setAttribute(TOKEN_KEY, token);
            response.addHeader("Set-Cookie", TOKEN_KEY + "=" + token + ";httpOnly");
        }
        else {
            String cookieToken = getCookieValue(request.getCookies(), TOKEN_KEY);
            if (token.equals(cookieToken) == false) {
                session.invalidate();
            }
        }
    }
 
    private String getCookieValue(Cookie[] cookies, String cookieName) {
        if (cookies == null) return null;
        for (Cookie cookie : cookies) {
            if (cookieName.equals(cookie.getName())) return cookie.getValue();
        }
        return null;
    }
 
    private String getRandomString() {
        return String.valueOf(System.currentTimeMillis()); // TODO: replace with more random value. I mean it!
    }
 
    public void init(FilterConfig filterConfig) throws ServletException {}
    public void destroy() {}
}
(1)It seems that soon all mayor browsers will support this feature as well, for example http://blogs.securiteam.com/index.php/archives/849
(2) As in http://www.owasp.org/index.php/Session_hijacking_attack
(3) Things are improving here as well: https://issues.apache.org/bugzilla/show_bug.cgi?id=44382