Offenlegung von Datenelementen an falsche Sitzung
Beschreibung
Offenlegung von Datenelementen an falsche Sitzung ist eine Schwachstelle, bei der das Produkt die Grenzen zwischen verschiedenen Benutzersitzungen nicht ordnungsgemäß durchsetzt, wodurch Daten der falschen Sitzung bereitgestellt oder von ihr verwendet werden können. Daten können von einer Sitzung in eine andere "durchsickern" durch Membervariablen von Singleton-Objekten, gemeinsam genutzte Pool-Objekte oder unsachgemäß eingeschränkte Variablen. Dies ist besonders häufig in Servlet-Umgebungen, wo Entwickler missverstehen, dass Servlets Singletons sind, die mehrere gleichzeitige Anfragen über verschiedene Threads verarbeiten, wodurch Benutzerdaten, die in Servlet-Memberfeldern gespeichert sind, über Sitzungen hinweg zugänglich werden.
Risiko
Sitzungsdaten-Offenlegung erzeugt schwerwiegende Vertraulichkeits- und Integritätsverletzungen. Benutzer können persönliche Informationen, Finanzdaten oder Authentifizierungsanmeldedaten anderer Benutzer sehen. In E-Commerce-Anwendungen könnten Benutzer die Warenkörbe oder Zahlungsinformationen anderer Kunden sehen. In Gesundheitssystemen könnten Patientendaten unbefugten Benutzern offengelegt werden. Die Schwachstelle ist besonders heimtückisch, weil sie sich nur unter bestimmten Timing-Bedingungen bei gleichzeitigen Anfragen manifestiert, was sie während des Testens schwer zu reproduzieren macht. Angreifer können absichtlich hohe Gleichzeitigkeit verursachen, um die Race Condition auszulösen und offengelegte Daten zu ernten.
Lösung
Speichern Sie niemals anfrage- oder sitzungsspezifische Daten in Memberfeldern von gemeinsam genutzten Objekten wie Servlets. Verwenden Sie lokale Variablen innerhalb von Methoden für anfragebezogene Daten. Für sitzungsbezogene Daten verwenden Sie explizit das HttpSession-Objekt. Implementieren Sie ordnungsgemäße Thread-Isolation unter Verwendung von ThreadLocal-Variablen, wenn thread-spezifischer Speicher benötigt wird. Verwenden Sie unveränderliche Objekte wo möglich. Führen Sie Code-Reviews durch, die speziell nach gemeinsam genutztem veränderlichem Zustand in nebenläufigen Kontexten suchen. Verwenden Sie statische Analysetools, die potenzielle Daten-Races und Sitzungsverwechslungs-Schwachstellen erkennen können. Erwägen Sie die Verwendung von anfragebezogenen Beans in Dependency-Injection-Frameworks.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Vertraulichkeit Anwendungsdaten lesen - Sensible Benutzerdaten können von einer Sitzung in eine andere durchsickern und persönliche Informationen, Anmeldedaten oder andere vertrauliche Daten unbefugten Benutzern offenlegen. |
| Integrität | Umfang: Integrität Anwendungsdaten modifizieren - Benutzer können versehentlich Daten anderer Sitzungen modifizieren, was Datenkorruption und unerwartetes Verhalten verursacht. |
Beispielcode
Anfälliger Code
// Anfällig: Servlet mit Membervariable die Benutzerdaten speichert
import javax.servlet.http.*;
import java.io.*;
public class VulnerableServlet extends HttpServlet {
// Anfällig: Membervariable über ALLE Anfragen/Sitzungen geteilt
private String userName;
private String userEmail;
private ShoppingCart cart;
private User currentUser;
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Thread 1: Benutzer "Alice" macht Anfrage
userName = request.getParameter("name"); // userName = "Alice"
// Kontextwechsel zu Thread 2: Benutzer "Bob" macht Anfrage
// userName = "Bob" (überschreibt Alices Name)
// Thread 1 setzt fort: Alice sieht Bobs Name!
response.getWriter().println("Hallo, " + userName);
// Anfällig: Benutzerdaten sickern zwischen Sitzungen
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Anfällig: Warenkorb als Membervariable gespeichert
cart = new ShoppingCart();
cart.addItem(request.getParameter("item"));
// Anfrage eines anderen Benutzers könnte 'cart' überschreiben
// bevor diese Anfrage abgeschlossen ist
processCheckout(cart); // Kann falschen Warenkorb verarbeiten!
}
}
// Anfällig: Singleton-Service mit veränderlichem Zustand
public class VulnerableUserService {
private static VulnerableUserService instance;
// Anfällig: Aktueller Benutzer im Singleton gespeichert
private User currentUser;
private String authToken;
public static VulnerableUserService getInstance() {
if (instance == null) {
instance = new VulnerableUserService();
}
return instance;
}
public void setCurrentUser(User user) {
// Anfällig: Ein anderer Thread kann dies überschreiben
this.currentUser = user;
}
public User getCurrentUser() {
// Anfällig: Kann falschen Benutzer zurückgeben
return this.currentUser;
}
public void performUserAction() {
// Anfällig: currentUser kann sich geändert haben
// zwischen setCurrentUser und diesem Aufruf
auditLog.log(currentUser.getName() + " führte Aktion aus");
// Kann falschen Benutzer protokollieren!
}
}
// Anfällig: Verbindungspool gibt falsche Benutzerverbindung zurück
public class VulnerableConnectionPool {
private Connection cachedConnection;
private String lastUserId;
public Connection getConnection(String userId) {
// Anfällig: Zwischengespeicherte Verbindung mit Benutzerkontext
if (cachedConnection == null || !lastUserId.equals(userId)) {
cachedConnection = createConnection(userId);
lastUserId = userId;
}
// Anfällig: Ein anderer Thread kann cachedConnection geändert haben
// zwischen Prüfung und Rückgabe
return cachedConnection;
}
}
// Anfällig: Statischer Cache lässt Daten zwischen Benutzern durchsickern
public class VulnerableReportGenerator {
// Anfällig: Statischer Cache für alle Sitzungen zugänglich
private static Map<String, Report> reportCache = new HashMap<>();
private static String lastReportUser;
public Report generateReport(String userId) {
Report report = createReport(userId);
// Anfällig: Caching ohne ordnungsgemäße Isolation
reportCache.put("lastReport", report);
lastReportUser = userId;
return report;
}
public Report getLastReport() {
// Anfällig: Jeder Benutzer kann auf zuletzt generierten Bericht zugreifen
return reportCache.get("lastReport");
}
}
# Anfällig: Flask-Anwendung mit Zustand auf Modulebene
from flask import Flask, request
app = Flask(__name__)
# Anfällig: Variablen auf Modulebene zwischen Anfragen geteilt
current_user = None
user_cart = {}
@app.route('/login', methods=['POST'])
def login():
global current_user
# Anfällig: current_user zwischen ALLEN Anfragen geteilt
current_user = request.form['username']
return f"Angemeldet als {current_user}"
@app.route('/profile')
def profile():
# Anfällig: Kann falschen Benutzer wegen Race Condition zurückgeben
return f"Profil für {current_user}"
@app.route('/add_to_cart', methods=['POST'])
def add_to_cart():
global user_cart
item = request.form['item']
# Anfällig: user_cart zwischen Sitzungen geteilt
user_cart[item] = user_cart.get(item, 0) + 1
return f"{item} hinzugefügt"
Korrigierter Code
// Korrigiert: Servlet verwendet lokale Variablen und Session
import javax.servlet.http.*;
import java.io.*;
public class SecureServlet extends HttpServlet {
// Korrigiert: Keine Membervariablen für Benutzerdaten
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Korrigiert: Lokale Variable verwenden - thread-sicher
String userName = request.getParameter("name");
// Korrigiert: Benutzerdaten in Session speichern
HttpSession session = request.getSession();
session.setAttribute("userName", userName);
response.getWriter().println("Hallo, " + userName);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
// Korrigiert: Warenkorb des Benutzers aus Session holen
HttpSession session = request.getSession();
ShoppingCart cart = (ShoppingCart) session.getAttribute("cart");
if (cart == null) {
cart = new ShoppingCart();
session.setAttribute("cart", cart);
}
// Korrigiert: Auf Session synchronisieren für Thread-Sicherheit
synchronized (session) {
cart.addItem(request.getParameter("item"));
}
processCheckout(cart);
}
}
// Korrigiert: Thread-sicherer Service mit ThreadLocal
public class SecureUserService {
private static SecureUserService instance;
// Korrigiert: ThreadLocal für pro-Thread Benutzerkontext verwenden
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) {
// Korrigiert: ThreadLocal bietet pro-Thread Speicherung
currentUser.set(user);
}
public User getCurrentUser() {
// Korrigiert: Gibt Benutzer dieses Threads zurück
return currentUser.get();
}
public void performUserAction() {
User user = currentUser.get();
if (user == null) {
throw new IllegalStateException("Kein Benutzer im Kontext");
}
auditLog.log(user.getName() + " führte Aktion aus");
}
// Korrigiert: ThreadLocal bereinigen um Speicherlecks zu verhindern
public void clearContext() {
currentUser.remove();
authToken.remove();
}
}
// Korrigiert: Ordnungsgemäße Anfragekontextverwaltung
public class SecureRequestContext {
// Korrigiert: Anfragebezogener Kontext mit 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;
}
}
// Korrigiert: Servlet-Filter für Kontextverwaltung
public class ContextFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
try {
// Korrigiert: Kontext zu Beginn der Anfrage initialisieren
SecureRequestContext.initContext((HttpServletRequest) request);
SecureUserService.getInstance().setCurrentUser(
getUserFromSession((HttpServletRequest) request)
);
chain.doFilter(request, response);
} finally {
// Korrigiert: Kontext immer bereinigen
SecureRequestContext.clearContext();
SecureUserService.getInstance().clearContext();
}
}
}
// Korrigiert: Sitzungsbezogene Beans in Spring
import org.springframework.web.context.annotation.SessionScope;
import org.springframework.stereotype.Component;
@Component
@SessionScope // Korrigiert: Eine Instanz pro Sitzung
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);
}
}
// Korrigiert: Anfragebezogener Service
@Component
@RequestScope // Korrigiert: Eine Instanz pro Anfrage
public class RequestScopedService {
private User currentUser;
public void setCurrentUser(User user) {
this.currentUser = user;
}
public User getCurrentUser() {
return currentUser;
}
}
# Korrigiert: Flask-Anwendung mit ordnungsgemäßer Session-Behandlung
from flask import Flask, request, session, g
from functools import wraps
app = Flask(__name__)
app.secret_key = 'sicherer-geheimer-schlüssel'
# Korrigiert: Anfragebezogener Speicher mit Flasks g-Objekt
@app.before_request
def before_request():
# Korrigiert: g ist anfrage-lokal, sicher für gleichzeitige Anfragen
g.user = session.get('user')
g.cart = session.get('cart', {})
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
# Korrigiert: In Session speichern, nicht globaler Variable
session['user'] = username
return f"Angemeldet als {username}"
@app.route('/profile')
def profile():
# Korrigiert: Zugriff vom anfrage-lokalen g-Objekt
if g.user is None:
return "Nicht angemeldet", 401
return f"Profil für {g.user}"
@app.route('/add_to_cart', methods=['POST'])
def add_to_cart():
item = request.form['item']
# Korrigiert: Warenkorb in Session gespeichert, pro Benutzer isoliert
cart = session.get('cart', {})
cart[item] = cart.get(item, 0) + 1
session['cart'] = cart
return f"{item} hinzugefügt"
@app.teardown_request
def teardown_request(exception=None):
# Korrigiert: Warenkorb zurück in Session speichern
if hasattr(g, 'cart'):
session['cart'] = g.cart
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgelistet. Das Schwachstellenmuster ist jedoch häufig in:
- Java-Servlet-Anwendungen mit unsachgemäßer Zustandsverwaltung
- Multi-Thread-Webanwendungen mit gemeinsam genutztem veränderlichem Zustand
- Caching-Systemen, die Benutzerdaten nicht ordnungsgemäß isolieren
Referenzen
- 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."