Exposure of Data Element to Wrong Session
Description
Exposure of Data Element to Wrong Session is a vulnerability where the product fails to properly enforce boundaries between different user sessions, allowing data to be provided to or used by the wrong session. Data can "bleed" from one session to another through member variables of singleton objects, shared pool objects, or improperly scoped variables. This is particularly common in Servlet environments where developers misunderstand that Servlets are singletons handling multiple simultaneous requests via different threads, causing user data stored in Servlet member fields to become accessible across sessions.
Risk
Session data exposure creates severe confidentiality and integrity violations. Users may see other users' personal information, financial data, or authentication credentials. In e-commerce applications, users might see other customers' shopping carts or payment information. In healthcare systems, patient data could be exposed to unauthorized users. The vulnerability is particularly insidious because it may only manifest under specific timing conditions with concurrent requests, making it difficult to reproduce during testing. Attackers can deliberately cause high concurrency to trigger the race condition and harvest exposed data.
Solution
Never store per-request or per-session data in member fields of shared objects like Servlets. Use local variables within methods for request-scoped data. For session-scoped data, use the HttpSession object explicitly. Implement proper thread isolation using ThreadLocal variables when thread-specific storage is needed. Use immutable objects where possible. Conduct code reviews specifically looking for shared mutable state in concurrent contexts. Use static analysis tools that can detect potential data races and session confusion vulnerabilities. Consider using request-scoped beans in dependency injection frameworks.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Sensitive user data can leak from one session to another, exposing personal information, credentials, or other confidential data to unauthorized users. |
| Integrity | Scope: Integrity Modify Application Data - Users may inadvertently modify data belonging to other sessions, causing data corruption and unexpected behavior. |
Example Code
Vulnerable Code
// Vulnerable: Servlet with member variable storing user data
import javax.servlet.http.*;
import java.io.*;
public class VulnerableServlet extends HttpServlet {
// Vulnerable: Member variable shared across ALL requests/sessions
private String userName;
private String userEmail;
private ShoppingCart cart;
private User currentUser;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Thread 1: User "Alice" makes request
userName = request.getParameter("name"); // userName = "Alice"
// Context switch to Thread 2: User "Bob" makes request
// userName = "Bob" (overwrites Alice's name)
// Thread 1 resumes: Alice sees Bob's name!
response.getWriter().println("Hello, " + userName);
// Vulnerable: User data bleeds between sessions
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Vulnerable: Cart stored as member variable
cart = new ShoppingCart();
cart.addItem(request.getParameter("item"));
// Another user's request might overwrite 'cart'
// before this request completes
processCheckout(cart); // May process wrong user's cart!
}
}
// Vulnerable: Singleton service with mutable state
public class VulnerableUserService {
private static VulnerableUserService instance;
// Vulnerable: Current user stored in singleton
private User currentUser;
private String authToken;
public static VulnerableUserService getInstance() {
if (instance == null) {
instance = new VulnerableUserService();
}
return instance;
}
public void setCurrentUser(User user) {
// Vulnerable: Another thread can overwrite this
this.currentUser = user;
}
public User getCurrentUser() {
// Vulnerable: May return wrong user
return this.currentUser;
}
public void performUserAction() {
// Vulnerable: currentUser may have changed
// between setCurrentUser and this call
auditLog.log(currentUser.getName() + " performed action");
// May log wrong user!
}
}
// Vulnerable: Connection pool returning wrong user's connection
public class VulnerableConnectionPool {
private Connection cachedConnection;
private String lastUserId;
public Connection getConnection(String userId) {
// Vulnerable: Cached connection with user context
if (cachedConnection == null || !lastUserId.equals(userId)) {
cachedConnection = createConnection(userId);
lastUserId = userId;
}
// Vulnerable: Another thread may have changed cachedConnection
// between the check and the return
return cachedConnection;
}
}
// Vulnerable: Static cache leaking data between users
public class VulnerableReportGenerator {
// Vulnerable: Static cache accessible to all sessions
private static Map<String, Report> reportCache = new HashMap<>();
private static String lastReportUser;
public Report generateReport(String userId) {
Report report = createReport(userId);
// Vulnerable: Caching without proper isolation
reportCache.put("lastReport", report);
lastReportUser = userId;
return report;
}
public Report getLastReport() {
// Vulnerable: Any user can access last generated report
return reportCache.get("lastReport");
}
}
# Vulnerable: Flask application with module-level state
from flask import Flask, request
app = Flask(__name__)
# Vulnerable: Module-level variables shared between requests
current_user = None
user_cart = {}
@app.route('/login', methods=['POST'])
def login():
global current_user
# Vulnerable: current_user shared between ALL requests
current_user = request.form['username']
return f"Logged in as {current_user}"
@app.route('/profile')
def profile():
# Vulnerable: May return wrong user due to race condition
return f"Profile for {current_user}"
@app.route('/add_to_cart', methods=['POST'])
def add_to_cart():
global user_cart
item = request.form['item']
# Vulnerable: user_cart shared between sessions
user_cart[item] = user_cart.get(item, 0) + 1
return f"Added {item}"
Fixed Code
// Fixed: Servlet using local variables and session
import javax.servlet.http.*;
import java.io.*;
public class SecureServlet extends HttpServlet {
// Fixed: No member variables for user data
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Fixed: Use local variable - thread-safe
String userName = request.getParameter("name");
// Fixed: Store user data in session
HttpSession session = request.getSession();
session.setAttribute("userName", userName);
response.getWriter().println("Hello, " + userName);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Fixed: Get user's cart from session
HttpSession session = request.getSession();
ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
if (cart == null) {
cart = new ShoppingCart();
session.setAttribute("cart", cart);
}
// Fixed: Synchronize on session for thread safety
synchronized (session) {
cart.addItem(request.getParameter("item"));
}
processCheckout(cart);
}
}
// Fixed: Thread-safe service using ThreadLocal
public class SecureUserService {
private static SecureUserService instance;
// Fixed: Use ThreadLocal for per-thread user context
private ThreadLocal<User> currentUser = new ThreadLocal<>();
private ThreadLocal<String> authToken = new ThreadLocal<>();
public static synchronized SecureUserService getInstance() {
if (instance == null) {
instance = new SecureUserService();
}
return instance;
}
public void setCurrentUser(User user) {
// Fixed: ThreadLocal provides per-thread storage
currentUser.set(user);
}
public User getCurrentUser() {
// Fixed: Returns this thread's user
return currentUser.get();
}
public void performUserAction() {
User user = currentUser.get();
if (user == null) {
throw new IllegalStateException("No user in context");
}
auditLog.log(user.getName() + " performed action");
}
// Fixed: Clean up ThreadLocal to prevent memory leaks
public void clearContext() {
currentUser.remove();
authToken.remove();
}
}
// Fixed: Proper request context management
public class SecureRequestContext {
// Fixed: Request-scoped context using ThreadLocal
private static final ThreadLocal<RequestContext> context =
new ThreadLocal<>();
public static void initContext(HttpServletRequest request) {
RequestContext ctx = new RequestContext();
ctx.userId = (String) request.getSession().getAttribute("userId");
ctx.sessionId = request.getSession().getId();
ctx.requestTime = System.currentTimeMillis();
context.set(ctx);
}
public static RequestContext getContext() {
return context.get();
}
public static void clearContext() {
context.remove();
}
private static class RequestContext {
String userId;
String sessionId;
long requestTime;
}
}
// Fixed: Servlet filter for context management
public class ContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
try {
// Fixed: Initialize context at start of request
SecureRequestContext.initContext((HttpServletRequest) request);
SecureUserService.getInstance().setCurrentUser(
getUserFromSession((HttpServletRequest) request)
);
chain.doFilter(request, response);
} finally {
// Fixed: Always clean up context
SecureRequestContext.clearContext();
SecureUserService.getInstance().clearContext();
}
}
}
// Fixed: Session-scoped beans in Spring
import org.springframework.web.context.annotation.SessionScope;
import org.springframework.stereotype.Component;
@Component
@SessionScope // Fixed: One instance per session
public class UserShoppingCart {
private List<CartItem> items = new ArrayList<>();
public void addItem(CartItem item) {
items.add(item);
}
public List<CartItem> getItems() {
return Collections.unmodifiableList(items);
}
}
// Fixed: Request-scoped service
@Component
@RequestScope // Fixed: One instance per request
public class RequestScopedService {
private User currentUser;
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
}
# Fixed: Flask application with proper session handling
from flask import Flask, request, session, g
from functools import wraps
app = Flask(__name__)
app.secret_key = 'secure-secret-key'
# Fixed: Request-scoped storage using Flask's g object
@app.before_request
def before_request():
# Fixed: g is request-local, safe for concurrent requests
g.user = session.get('user')
g.cart = session.get('cart', {})
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
# Fixed: Store in session, not global variable
session['user'] = username
return f"Logged in as {username}"
@app.route('/profile')
def profile():
# Fixed: Access from request-local g object
if g.user is None:
return "Not logged in", 401
return f"Profile for {g.user}"
@app.route('/add_to_cart', methods=['POST'])
def add_to_cart():
item = request.form['item']
# Fixed: Cart stored in session, isolated per user
cart = session.get('cart', {})
cart[item] = cart.get(item, 0) + 1
session['cart'] = cart
return f"Added {item}"
@app.teardown_request
def teardown_request(exception=None):
# Fixed: Save cart back to session
if hasattr(g, 'cart'):
session['cart'] = g.cart
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is common in:
- Java Servlet applications with improper state management
- Multi-threaded web applications with shared mutable state
- Caching systems that don't properly isolate user data
References
- MITRE Corporation. "CWE-488: Exposure of Data Element to Wrong Session." https://cwe.mitre.org/data/definitions/488.html
- Oracle. "The Java Servlet Specification - Threading Issues."
- OWASP. "Session Management Cheat Sheet."