6
votes

It's a common best practice to renew the HTTP session when logging in a user. This will force a new session ID, avoiding session fixation vulnerabilities.

Is there a preferred pattern for implementing this with CDI when @SessionScoped beans are involved? The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

For example, assume a session bean for storing user login information:

@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
    private int userId;
    private String username;
    private List<String> privileges;

    // Accessors omitted 
}

And another bean for managing the login:

@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {

    private String username;
    private String password;
    @Inject private SessionBean session;
    @Inject private SessionManager sessionManager;
    @Inject private PrivilegeManager privilegeManager;      

    public String doLogin() {
        String destinationUrl;

        if (validate(username, password)) {
            FacesContext context = FacesContext.getCurrentInstance();

            // force renewal of HTTP session
            context.getExternalContext().invalidateSession();

            // retrieve new session bean  ** No longer works with CDI **
            Application app = context.getApplication();
            session = app.evaluateExpressionGet(context, "#{sessionbean}", SessionBean.class);

            session.setUsername(username);
            session.setSessionId(sessionManager.createNewSession(username));
            session.setPrivileges(privilegeManager.getPrivileges(username));

            destinationUrl = createLandingPageUrl();

        } else {
            destinationUrl = createFailureUrl("Unknown user or password");
        }

        return destinationUrl;
    }
}

With Managed Beans this would retrieve a new SessionBean, but with CDI, the code above would just return the same SessionBean. Any recommendations or clever ideas?

2
I have no solution and I'm not sure if this is real best practice, looks somewhat awkwad to me. Shouldn't this be done by the container or e.g. by servlet filters? Nevertheless I upvoted because this questions seemes clear, asked well and a solid answer on SO would probably help others encountering the same challenge.Selaron
I was a little surprised as well about the approach until I tried reading up on Session fixation. I'll have to think about this a little more since there seems to be something weird about your code which I cannot grasp. I might have worked by 'chance' when using JSF managed beans (with cdi btw they are still managed beans, just managed by some other container. ).I'll get back to thls later (unless BalusC kicks in)Kukeltje
Can you create a minimal reproducible example? No SessionManager, no PrivilegeManager and a simple xhtml page?Kukeltje
@Kukeltje, working on it. I want to do my due diligence before just throwing it out there.Didjit
Why it is not a security issue in our case (I think) is that we, on logging in, set an additional cookie (hashed token). This token is also stored in the server in the session information and is in a servlet filter checked with each request. So even if the attacker has 'session fixation' (in whatever form) he/she/... cannot access the pages since the authentication cookie is missing there. Like in the image in owasp.org/index.php/Session_fixation, the response to '5' contains this cookie and that is not known to atacker, so 6 without this cookie will fail.Kukeltje

2 Answers

8
votes

The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

Then don't invalidate the session, but change the session ID. In other words, don't use HttpSession#invalidate(), but use HttpServletRequest#changeSessionId() (new since Servlet 3.1, which you should undoubtedly already be using given that you're using JSF 2.3).

In code, replace

// force renewal of HTTP session object
context.getExternalContext().invalidateSession();

by

// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();

This basically changes the JSESSIONID cookie without changing the HttpSession. It's perfect for session fixation prevention.

Explicitly invalidating the session is usually only useful during logout.

0
votes

I'm going to restrict this answer to be solely about CDI since I am not a security expert. I also don't know whether the general thing being asked for is a good idea or not. Regardless, here is how I think you would do what you're asking for.

Expressed in purely CDI terms, the question can be rephrased like:

I have an object that I know came from a particular Context. I know the lifecycle of objects produced by this Context. How can I properly tell the Context to invalidate the current object that it is managing, and load or create a new one?

The general approach is going to be:

  • @Inject a Provider<SessionBean> instead of SessionBean directly (this will let you ask CDI for the "new" object properly)
  • @Inject a BeanManager (so you can get the right Context that manages SessionScoped objects)
  • ask the BeanManager to give you the AlterableContext corresponding to the SessionScoped annotation
  • tell the AlterableContext to destroy the current bean's contextual instance
  • call Provider.get() to cause a new one to be created

So the relevant parts of your doLogin method might look like this (untested):

final AlterableContext context = (AlterableContext) this.beanManager.getContext(SessionScoped.class);
assert context != null;

final Bean<?> bean = beanManager.resolve(beanManager.getBeans(SessionBean.class));
assert bean != null;

context.destroy(bean);

final SessionBean newSessionBean = this.sessionBeanProvider.get();
assert newSessionBean != null;

I think that should work.