Skip to content

Commit

Permalink
Fix Spring session fixation&stale session issues (#52)
Browse files Browse the repository at this point in the history
Two problems solved:

- Fix session fixation vulnerability
- Fix stale Hazelcast session issue

Session fixation vulnerability occurred with Spring Security MVC
 as it used isRequestedSessionIdValid method on HttpServletRequest
 interface which was not implemented before this commit.

Stale Hazelcast session issue occurs when a request comes in with a
 valid JSESSIONID and a hazelcast.sessionId that corresponds to
 another Hazelcast session. In this case, we just used the existing
 session and used the incoming JSESSIONID to find the corresponding
 hazelcast.sessionId and Hazelcast session. Now we let the incoming
 hazelcast.sessionId to override the hazelcast.sessionId that
 corresponds to the request's JSESSIONID.

Fix #47
  • Loading branch information
emre-aydin authored Jul 25, 2017
1 parent 7bda7b0 commit 73d7e7e
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/main/java/com/hazelcast/web/WebFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,13 @@ public HazelcastHttpSession getSession(final boolean create) {
return hazelcastSession;
}

@Override
public boolean isRequestedSessionIdValid() {
return hazelcastSession != null && hazelcastSession.isValid();
}

private HazelcastHttpSession readSessionFromLocal() {
// following chunk is executed _only_ when session is invalidated and getSession is called on the request
String invalidatedOriginalSessionId = null;
if (hazelcastSession != null && !hazelcastSession.isValid()) {
LOGGER.finest("Session is invalid!");
Expand All @@ -374,9 +380,17 @@ private HazelcastHttpSession readSessionFromLocal() {
} else if (hazelcastSession != null) {
return hazelcastSession;
}

HttpSession originalSession = getOriginalSession(false);
if (originalSession != null) {
String hazelcastSessionId = originalSessions.get(originalSession.getId());
String hazelcastSessionIdFromRequest = findHazelcastSessionIdFromRequest();
// hazelcast.sessionId from the request overrides hazelcast.sessionId corresponding to jsessionid from
// the request
if (hazelcastSessionIdFromRequest != null && !hazelcastSessionIdFromRequest.equals(hazelcastSessionId)) {
hazelcastSessionId = hazelcastSessionIdFromRequest;
}

if (hazelcastSessionId != null) {
hazelcastSession = getSessionWithId(hazelcastSessionId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@
import com.hazelcast.test.annotation.QuickTest;
import com.hazelcast.wm.test.ServletContainer;
import com.hazelcast.wm.test.TomcatServer;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.cookie.Cookie;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.session.SessionRegistry;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import static org.junit.Assert.assertEquals;
Expand All @@ -43,6 +47,83 @@ protected ServletContainer getServletContainer(int port, String sourceDir, Strin
return new TomcatServer(port, sourceDir, serverXml);
}

// https://github.com/hazelcast/hazelcast-wm/issues/47
@Test
public void testSessionFixationProtectionLostTomcatSessionId() throws Exception {
// Scenario: An initial request is made to the server before authentication that creates a tomcat session ID and
// a hazlecast session ID (e.g. a login page). Next, an authentication request is made but only the Hazelcast
// session ID is provided. It is expected that the original hazlecast session should be destroyed.

// Create a session so that a Tomcat and Hazelcast session ID is created
SpringSecuritySession sss = createSession(null, this.serverPort1);

// Remove the Tomcat session ID cookie from the request
List<Cookie> cookies = sss.cookieStore.getCookies();
sss.cookieStore.clear();
for (Cookie cookie : cookies) {
if (!SESSION_ID_COOKIE_NAME.equals(cookie.getName())) {
sss.cookieStore.addCookie(cookie);
}
}

String originalHazelcastSessionId = sss.getHazelcastSessionId();

// Login with only the Hazelcast session ID provided
sss = login(sss, false);

String hazelcastSessionId = sss.getHazelcastSessionId();

// Verify that the original hazelcast session ID was invalidated
assertNotEquals(originalHazelcastSessionId, hazelcastSessionId);
}

// https://github.com/hazelcast/hazelcast-wm/issues/47
@Test
public void testStaleLocalCache() throws Exception {
// Scenario: There are two server nodes (1 & 2) behind a load balancer. Each node handles a request prior to
// authentication so that both nodes have the Hazlecast session ID cached locally against a Tomcat session ID.
// Say node '1' performs the authentication on the login request. Node '2' should not attempt to use the
// original unauthenticated hazelcast session that was destroyed by node '1'.

// Create initial session on node 1
SpringSecuritySession sss = createSession(null, this.serverPort1);

// Get the cookies for the initial request to node 1
Cookie node1InitialTomcatCookie = getCookie(sss, SESSION_ID_COOKIE_NAME);
Cookie hazelcastCookiePreAuthentication = getCookie(sss, HZ_SESSION_ID_COOKIE_NAME);

// Make a request to node 2 with the hazelcast session ID
sss.cookieStore.clear();
sss.cookieStore.addCookie(hazelcastCookiePreAuthentication);
request("hello.jsp", this.serverPort2, sss.cookieStore);

// Get the tomcat cookie for node 2
Cookie node2InitialTomcatCookie = getCookie(sss, SESSION_ID_COOKIE_NAME);

// Login using node 1
sss.cookieStore.clear();
sss.cookieStore.addCookie(hazelcastCookiePreAuthentication);
sss.cookieStore.addCookie(node1InitialTomcatCookie);

sss = login(sss, false);

// Get the new hazelcast cookie
Cookie hazelcastAuthPostAuthentication = getCookie(sss, HZ_SESSION_ID_COOKIE_NAME);

HttpResponse node1Response = request("hello.jsp", this.serverPort1, sss.cookieStore);
// Request should not be re-directed to login
Assert.assertNotEquals(302, node1Response.getStatusLine().getStatusCode());

// Make a request to node 2
sss.cookieStore.clear();
sss.cookieStore.addCookie(node2InitialTomcatCookie);
sss.cookieStore.addCookie(hazelcastAuthPostAuthentication);

HttpResponse node2Response = request("hello.jsp", this.serverPort2, sss.cookieStore);
// Request should not be re-directed to login
Assert.assertNotEquals(302, node2Response.getStatusLine().getStatusCode());
}

@Test
public void test_issue_3049() throws Exception {
Set<ApplicationContext> applicationContextSet =
Expand Down Expand Up @@ -106,4 +187,26 @@ public void testChangeSessionIdAfterLogin() throws Exception {
assertNotEquals(jsessionIdBeforeLogin, sss.getSessionId());
assertNotEquals(hzSessionIdBeforeLogin, sss.getHazelcastSessionId());
}

private Cookie getCookie(final SpringSecuritySession sss, final String cookieName) {
if (sss.cookieStore.getCookies() != null) {
for (org.apache.http.cookie.Cookie cookie : sss.cookieStore.getCookies()) {
if (cookie.getName().equals(cookieName)) {
return cookie;
}
}
}
return null;
}

private SpringSecuritySession createSession(SpringSecuritySession springSecuritySession, final int serverPort)
throws Exception {
if (springSecuritySession == null) {
springSecuritySession = new SpringSecuritySession();
}

request(RequestType.POST_REQUEST, SPRING_SECURITY_LOGIN_URL, serverPort, springSecuritySession.cookieStore);

return springSecuritySession;
}
}

0 comments on commit 73d7e7e

Please sign in to comment.