序
本文主要讲一下session fixation attacks以及spring security对它的防范。
session fixation attacks
会话固定攻击,是利用那些登录前和登录之后sessionId没有变化的漏洞来获取登录态,进而获取用户的相关信息等。
servlet3.1规范
servlet3.1规范中,HttpServletRequest.java明确规定了一个changeSessionId的方法
tomcat-embed-core-8.5.23-sources.jar!/javax/servlet/http/HttpServletRequest.java
/**
* Changes the session ID of the session associated with this request. This
* method does not create a new session object it only changes the ID of the
* current session.
*
* @return the new session ID allocated to the session
* @see HttpSessionIdListener
* @since Servlet 3.1
*/
public String changeSessionId();
SessionAuthenticationStrategy
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.java
/**
* Allows pluggable support for HttpSession-related behaviour when an authentication
* occurs.
*
* Typical use would be to make sure a session exists or to change the session Id to guard
* against session-fixation attacks.
*
* @author Luke Taylor
* @since
*/
public interface SessionAuthenticationStrategy {
/**
* Performs Http session-related functionality when a new authentication occurs.
*
* @throws SessionAuthenticationException if it is decided that the authentication is
* not allowed for the session. This will typically be because the user has too many
* sessions open at once.
*/
void onAuthentication(Authentication authentication, HttpServletRequest request,
HttpServletResponse response) throws SessionAuthenticationException;
}
spring security 提供了SessionAuthenticationStrategy接口,用来在登陆成功之后的处理session相关逻辑,它有个抽象类AbstractSessionFixationProtectionStrategy
AbstractSessionFixationProtectionStrategy
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/session/AbstractSessionFixationProtectionStrategy.java
/**
* Called when a user is newly authenticated.
*
* If a session already exists, and matches the session Id from the client, a new
* session will be created, and the session attributes copied to it (if
* {@code migrateSessionAttributes} is set). If the client's requested session Id is
* invalid, nothing will be done, since there is no need to change the session Id if
* it doesn't match the current session.
*
* If there is no session, no action is taken unless the {@code alwaysCreateSession}
* property is set, in which case a session will be created if one doesn't already
* exist.
*/
public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
boolean hadSessionAlready = request.getSession(false) != null;
if (!hadSessionAlready && !alwaysCreateSession) {
// Session fixation isn't a problem if there's no session
return;
}
// Create new session if necessary
HttpSession session = request.getSession();
if (hadSessionAlready && request.isRequestedSessionIdValid()) {
String originalSessionId;
String newSessionId;
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// We need to migrate to a new session
originalSessionId = session.getId();
session = applySessionFixation(request);
newSessionId = session.getId();
}
if (originalSessionId.equals(newSessionId)) {
logger.warn("Your servlet container did not change the session ID when a new session was created. You will"
+ " not be adequately protected against session-fixation attacks");
}
onSessionChange(originalSessionId, session, authentication);
}
}
如果是servlet3.1的话,则spring security默认的SessionAuthenticationStrategy就是ChangeSessionIdAuthenticationStrategy
SessionManagementConfigurer
spring-security-config-4.2.3.RELEASE-sources.jar!/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java
/**
* Creates the default {@link SessionAuthenticationStrategy} for session fixation
* @return the default {@link SessionAuthenticationStrategy} for session fixation
*/
private static SessionAuthenticationStrategy createDefaultSessionFixationProtectionStrategy() {
try {
return new ChangeSessionIdAuthenticationStrategy();
}
catch (IllegalStateException e) {
return new SessionFixationProtectionStrategy();
}
}
ChangeSessionIdAuthenticationStrategy
spring-security-web-4.2.3.RELEASE-sources.jar!/org/springframework/security/web/authentication/session/ChangeSessionIdAuthenticationStrategy.java
/**
* Uses {@code HttpServletRequest.changeSessionId()} to protect against session fixation
* attacks. This is the default implementation for Servlet 3.1+.
*
* @author Rob Winch
* @since 3.2
*/
public final class ChangeSessionIdAuthenticationStrategy
extends AbstractSessionFixationProtectionStrategy {
private final Method changeSessionIdMethod;
public ChangeSessionIdAuthenticationStrategy() {
Method changeSessionIdMethod = ReflectionUtils
.findMethod(HttpServletRequest.class, "changeSessionId");
if (changeSessionIdMethod == null) {
throw new IllegalStateException(
"HttpServletRequest.changeSessionId is undefined. Are you using a Servlet 3.1+ environment?");
}
this.changeSessionIdMethod = changeSessionIdMethod;
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.authentication.session.
* AbstractSessionFixationProtectionStrategy
* #applySessionFixation(javax.servlet.http.HttpServletRequest)
*/
@Override
HttpSession applySessionFixation(HttpServletRequest request) {
ReflectionUtils.invokeMethod(this.changeSessionIdMethod, request);
return request.getSession();
}
}
通过反射调用changeSessionId方法,具体是调用Request#changeSessionId
Request#changeSessionId
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/connector/Request.java
/**
* Changes the session ID of the session associated with this request.
*
* @return the old session ID before it was changed
* @see javax.servlet.http.HttpSessionIdListener
* @since Servlet 3.1
*/
@Override
public String changeSessionId() {
Session session = this.getSessionInternal(false);
if (session == null) {
throw new IllegalStateException(
sm.getString("coyoteRequest.changeSessionId"));
}
Manager manager = this.getContext().getManager();
manager.changeSessionId(session);
String newSessionId = session.getId();
this.changeSessionId(newSessionId);
return newSessionId;
}
这里调用了manager.changeSessionId(session)
ManagerBase#changeSessionId(session)
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/session/ManagerBase.java
@Override
public void changeSessionId(Session session) {
String newId = generateSessionId();
changeSessionId(session, newId, true, true);
}
protected void changeSessionId(Session session, String newId,
boolean notifySessionListeners, boolean notifyContainerListeners) {
String oldId = session.getIdInternal();
session.setId(newId, false);
session.tellChangedSessionId(newId, oldId,
notifySessionListeners, notifyContainerListeners);
}
/**
* Generate and return a new session identifier.
* @return a new session id
*/
protected String generateSessionId() {
String result = null;
do {
if (result != null) {
// Not thread-safe but if one of multiple increments is lost
// that is not a big deal since the fact that there was any
// duplicate is a much bigger issue.
duplicates++;
}
result = sessionIdGenerator.generateSessionId();
} while (sessions.containsKey(result));
return result;
}
StandardSessionIdGenerator#generateSessionId
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/util/StandardSessionIdGenerator.java
public class StandardSessionIdGenerator extends SessionIdGeneratorBase {
@Override
public String generateSessionId(String route) {
byte random[] = new byte[16];
int sessionIdLength = getSessionIdLength();
// Render the result as a String of hexadecimal digits
// Start with enough space for sessionIdLength and medium route size
StringBuilder buffer = new StringBuilder(2 * sessionIdLength + 20);
int resultLenBytes = 0;
while (resultLenBytes < sessionIdLength) {
getRandomBytes(random);
for (int j = 0;
j < random.length && resultLenBytes < sessionIdLength;
j++) {
byte b1 = (byte) ((random[j] & 0xf0) >> 4);
byte b2 = (byte) (random[j] & 0x0f);
if (b1 < 10)
buffer.append((char) ('0' + b1));
else
buffer.append((char) ('A' + (b1 - 10)));
if (b2 < 10)
buffer.append((char) ('0' + b2));
else
buffer.append((char) ('A' + (b2 - 10)));
resultLenBytes++;
}
}
if (route != null && route.length() > 0) {
buffer.append('.').append(route);
} else {
String jvmRoute = getJvmRoute();
if (jvmRoute != null && jvmRoute.length() > 0) {
buffer.append('.').append(jvmRoute);
}
}
return buffer.toString();
}
}
这段是tomcat生成sessionId的逻辑
小结
spring security通过SessionAuthenticationStrategy,在登录成功之后进行相关session处理,如果servlet3.1+,则使用ChangeSessionIdAuthenticationStrategy来更换sessionId,以防范session fixation attacks。
doc
- Session fixation
- 会话固定攻击