Thursday, September 4, 2008

JSESSIONID Regeneration in Struts 2

Background

Whenever a user crosses an authentication boundary, the user's session ID should be regenerated. This concept applies to a user logging into an application, logging out, or when a user reauthenticates due to a risk-based authentication process. The regeneration of session IDs is an important practice that helps eliminate session fixation vulnerabilities and may limit the impact of session theft vulnerabilities prior to authentication.

For more information on Session Fixation vulnerabilities and Session ID regeneration practices, please see the OWASP pages below:

http://www.owasp.org/index.php/Session_Fixation
http://www.owasp.org/index.php/Session_Management#Regeneration_of_Session_Tokens

Session ID Regeneration in Traditional Java Web Applications

In a J2EE application, the user's JSESSIONID cookie should be regenerated and the previous session should be removed or deleted from the server. Example code below shows how this might be accomplished in a traditional Java web application.
public class ExampleLoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if( //authentication was successful ) {
request.getSession().invalidate();
HttpSession session = request.getSession(true);
session.setAttribute("AUTHENTICATED", new Boolean(true));
response.sendRedirect("PageRequiringAuthentication.jsp");
//Additional Code Would Normally Follow
Session ID Regeneration in Struts 2 Applications

In Struts 2 applications, developers typically don't directly interact with the HttpServletRequest, HTTPServletResponse, or HttpSession objects. With consideration of these factors, the solution discussed above for a traditional Java web application may not be appropriate for Struts 2.

I did a little research and through trial an error I came up with a Struts 2 specific solution for regenerating JSESSIONIDs. This solution utilizes the SessionAware interface. Please excuse the unrealistic authentication code...
package nickcoblentzblog.actions.sessions;

import java.util.Map;
import org.apache.struts2.interceptor.SessionAware;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.dispatcher.SessionMap;

public class Login extends ActionSupport implements SessionAware  {
private String userid;
private String password;
private Map session;

public String execute() {
if(userid.equals("admin") && password.equals("admin"))  {

/* Session ID Regeneration: Try #4 */
((SessionMap)this.session).invalidate();
this.session = ActionContext.getContext().getSession();
/* End Try #4 */

session.put("AUTHENTICATED", new Boolean(true));


return SUCCESS;
}
else
return ERROR;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

public void setSession(Map session) {
this.session = session;
}
}
To test this code, I followed the following procedure.

1. Cleared all browser cookies
2. Visited the Login JSP page
3. Used the Web Developer Toolbar to view my initial JSESSIONID
4. Logged into the application successfully
5. Used the Web Developer Toolbar to view my final JSESSIONID

The initial JSESSIONID value was:
AA4996C5E24BB8221BB27B23EA599F34

The final JSESSIONID value was:
325ED18851B93EBA542D2AE7926E7F8E

Based on these tests this solution appears to work successfully.

In case anyone is curious, here are a couple other ideas I toyed with:
/* Try # 1:
this.request.getSession().invalidate();
this.request.getSession(true);
*/

/* Try #2:
HTTPUtilities esapiHTTPUtilities = ESAPI.httpUtilities();
esapiHTTPUtilities.setCurrentHTTP(request, response);
try {
esapiHTTPUtilities.changeSessionIdentifier();
}
catch(Exception e) {
e.printStackTrace();
}
*/

/* Try #3:
((SessionMap)ActionContext.getContext().getSession()).invalidate();
*/


The code repository containing updated struts 2 modules can be found below:

http://code.google.com/p/struts2securityaddons/

Additionally, you can see discussion of these modules in my earlier blog posts:

4 comments:

Andrew van der Stock said...

OWASP's ESAPI has this functionality:

org.owasp.esapi.HTTPUtilities#changeSessionIdentifier()

It does everything you need and takes one line of code:

ESAPI.httpUtilities().changeSessionIdentifier( request );

thanks,
Andrew

Nick Coblentz said...

Andrew,

In my second attempt to regenerate JSESSIONIDs, I utilized the ESAPI library similar to how you described (See try #2 at the bottom of the post). This was done using an SVN snapshot of ESAPI from last month.

I chose not to use ESAPI as in try #2 for a couple of reasons.

1. I had to implement the ServletRequestAware and ServletResponseAware interfaces in addition to the SessionAware interface in order to utilize ESAPI. This added more code and complexity.

2. I still had to retrieve and set the session within the Struts Action using:

this.session = ActionContext.getContext().getSession();

Otherwise I would receive a null pointer exception when I tried to access or modify session variables.

I like the solution I settled on (Try #4), because it requires very little code and is easy to understand (for those familiar with Struts 2). If there’s something I'm missing that would make ESAPI and try #2 easier, please let me know.

- Nick

Rory said...

Thanks Nick, this was very helpful. I found the SessionMap, which then led me to this blog. I had the same scenario of sorts, needed to destroy the current session on logout.

Still need to figure out how to have the session destroyed on browser window close, or, in my case, when I reboot the server every few minutes while testing.

Off to read some other posts, soon to lead to refactoring I'm sure.

Anonymous said...

Gostei muito !