聊聊session fixation attacks

1,619 阅读3分钟

本文主要讲一下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.
 * <p>
 * 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.
	 * <p>
	 * 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.
	 * <p>
	 * 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