Trust Boundary Violation
Description
Trust Boundary Violation is a vulnerability where a product mixes trusted and untrusted data in the same data structure or structured message. A trust boundary represents a conceptual line separating untrusted data from trustworthy data, with validation logic enabling data to safely cross this boundary. When programs blur this distinction by combining validated and unvalidated data in shared structures like session objects, databases, or message queues, developers may inadvertently trust data that hasn't been properly validated, leading to security vulnerabilities.
Risk
Mixing trusted and untrusted data creates severe security risks because developers lose the ability to distinguish which data has been validated. User-supplied input stored alongside server-generated values in session objects may be mistakenly trusted in subsequent operations. Untrusted data injected into trusted message queues can trigger privileged operations. Data stored in shared caches or databases without clear provenance tracking can be used in security-critical decisions. This vulnerability enables various attacks including privilege escalation, injection attacks, and bypass of security controls when untrusted data is consumed as if it were trusted.
Solution
Establish and maintain well-defined trust boundaries throughout the application. Store trusted and untrusted data in separate data structures with clear naming conventions or type distinctions. Validate and sanitize all data before it crosses from untrusted to trusted contexts. Use type systems or wrapper classes to distinguish validated from unvalidated data. Implement validation checkpoints at trust boundaries rather than relying on implicit trust. Document trust assumptions for all data structures. Never store user-provided data in session objects without explicit validation and clear marking of its origin.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Bypass Protection Mechanism - Security safeguards can be circumvented through manipulation of mixed trusted/untrusted data sources, allowing attackers to inject malicious data that is later treated as trusted. |
| Integrity | Scope: Integrity Modify Application Data - Untrusted data mixed with trusted data can corrupt the trusted data store, leading to incorrect security decisions or data corruption. |
Example Code
Vulnerable Code
// Vulnerable: Storing untrusted input in session without validation
import javax.servlet.http.*;
public class VulnerableLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
String username = request.getParameter("usrname");
HttpSession session = request.getSession();
// Vulnerable: Untrusted input stored directly in session
// Later code may treat this as validated/trusted
if (session.getAttribute("ATTR_USR") == null) {
session.setAttribute("ATTR_USR", username);
}
// Vulnerable: Untrusted role from request stored in session
String role = request.getParameter("role");
session.setAttribute("USER_ROLE", role);
// Authentication happens later, but username is already "trusted"
if (authenticateUser(username, request.getParameter("password"))) {
// Even if auth fails, username is in session
}
}
}
// Later code trusts the session data
public class VulnerableProfileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession();
// Vulnerable: Assumes username was validated
String username = (String) session.getAttribute("ATTR_USR");
// Vulnerable: Uses untrusted role for authorization
String role = (String) session.getAttribute("USER_ROLE");
if ("admin".equals(role)) {
// Attacker can set role=admin in initial request
showAdminPanel(response);
}
// Vulnerable: Username used in SQL without validation
// (assumes session data is safe)
String query = "SELECT * FROM users WHERE name = '" + username + "'";
}
}
// Vulnerable: Mixing trusted and untrusted data in same map
public class VulnerableDataProcessor {
// Single map for all data - no distinction of trust
private Map<String, Object> dataStore = new HashMap<>();
public void processRequest(HttpServletRequest request) {
// Server-generated trusted data
dataStore.put("timestamp", System.currentTimeMillis());
dataStore.put("serverIP", getServerIP());
dataStore.put("sessionId", generateSecureSessionId());
// Vulnerable: Untrusted user input mixed with trusted data
dataStore.put("username", request.getParameter("username"));
dataStore.put("email", request.getParameter("email"));
dataStore.put("preference", request.getParameter("pref"));
}
public void processData() {
// No way to know which data is trusted vs untrusted
String username = (String) dataStore.get("username"); // Untrusted!
Long timestamp = (Long) dataStore.get("timestamp"); // Trusted
// Developer might forget username is untrusted
log("User " + username + " accessed at " + timestamp); // Log injection!
}
}
// Vulnerable: Message queue mixing trust levels
public class VulnerableMessageProcessor {
public void processMessage(Message message) {
// Vulnerable: No distinction between system and user messages
String messageType = message.getHeader("type");
String payload = message.getBody();
// User could inject type=ADMIN_COMMAND
if ("ADMIN_COMMAND".equals(messageType)) {
executeAdminCommand(payload); // Dangerous!
}
}
public void queueUserMessage(String userInput) {
Message message = new Message();
message.setHeader("type", "USER_MESSAGE");
message.setBody(userInput);
// User can manipulate the message before it's queued
messageQueue.add(message);
}
}
# Vulnerable: Python example mixing trust levels
from flask import Flask, request, session
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def vulnerable_login():
# Vulnerable: Store untrusted data before validation
session['username'] = request.form['username']
session['preferred_language'] = request.form['lang']
# User-provided role stored directly
session['role'] = request.form.get('role', 'user')
# Later validate credentials (but data already in session)
if check_credentials(request.form['username'], request.form['password']):
session['authenticated'] = True
else:
session['authenticated'] = False
return redirect('/dashboard')
@app.route('/admin')
def vulnerable_admin():
# Vulnerable: Trust session data without verification
if session.get('role') == 'admin':
# Attacker set role=admin during login
return render_admin_panel()
return "Access denied"
# Vulnerable: Mixed data in single dictionary
class VulnerableUserData:
def __init__(self):
self.data = {}
def set_system_data(self, user_id):
# Trusted system-generated data
self.data['created_at'] = datetime.now()
self.data['internal_id'] = generate_uuid()
def set_user_data(self, form_data):
# Untrusted user input mixed with system data
self.data['name'] = form_data['name']
self.data['bio'] = form_data['bio'] # Could contain XSS
Fixed Code
// Fixed: Clear separation of trusted and untrusted data
import javax.servlet.http.*;
public class SecureLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) {
String username = request.getParameter("usrname");
String password = request.getParameter("password");
// Fixed: Validate BEFORE storing in session
if (!isValidUsername(username)) {
response.sendError(400, "Invalid username format");
return;
}
// Fixed: Authenticate BEFORE storing any user data
User authenticatedUser = authenticateUser(username, password);
if (authenticatedUser == null) {
response.sendError(401, "Authentication failed");
return;
}
HttpSession session = request.getSession(true);
// Fixed: Store validated, server-verified data
// Use typed wrapper to indicate trust level
session.setAttribute("USER", new TrustedUser(
authenticatedUser.getId(),
authenticatedUser.getUsername(),
authenticatedUser.getRoles() // Roles from database, not request
));
// Fixed: Explicitly mark session as authenticated
session.setAttribute("AUTHENTICATED", Boolean.TRUE);
}
private boolean isValidUsername(String username) {
return username != null &&
username.matches("^[a-zA-Z0-9_]{3,20}$");
}
}
// Fixed: Typed wrapper indicating trust status
public final class TrustedUser {
private final String id;
private final String username;
private final Set<String> roles;
private final Instant validatedAt;
// Can only be created with validated data
public TrustedUser(String id, String username, Set<String> roles) {
this.id = Objects.requireNonNull(id);
this.username = Objects.requireNonNull(username);
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
this.validatedAt = Instant.now();
}
// Immutable getters only
public String getId() { return id; }
public String getUsername() { return username; }
public Set<String> getRoles() { return roles; }
public Instant getValidatedAt() { return validatedAt; }
}
// Fixed: Profile servlet using typed trusted data
public class SecureProfileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) {
HttpSession session = request.getSession(false);
if (session == null) {
response.sendError(401, "Not authenticated");
return;
}
// Fixed: Use typed object - compiler enforces trust
TrustedUser user = (TrustedUser) session.getAttribute("USER");
Boolean authenticated = (Boolean) session.getAttribute("AUTHENTICATED");
if (user == null || !Boolean.TRUE.equals(authenticated)) {
response.sendError(401, "Not authenticated");
return;
}
// Fixed: Roles are from trusted source (database)
if (user.getRoles().contains("ADMIN")) {
showAdminPanel(response);
}
// Fixed: Use parameterized query with validated username
String query = "SELECT * FROM users WHERE id = ?";
// Use user.getId() with prepared statement
}
}
// Fixed: Separate data stores for different trust levels
public class SecureDataProcessor {
// Fixed: Separate structures for different trust levels
private final TrustedData trustedData = new TrustedData();
private final UntrustedInput untrustedInput = new UntrustedInput();
public void processRequest(HttpServletRequest request) {
// Trusted data - generated by server
trustedData.setTimestamp(System.currentTimeMillis());
trustedData.setServerIP(getServerIP());
trustedData.setSessionId(generateSecureSessionId());
// Fixed: Untrusted data kept separate
untrustedInput.setUsername(request.getParameter("username"));
untrustedInput.setEmail(request.getParameter("email"));
untrustedInput.setPreference(request.getParameter("pref"));
}
public void processData() {
// Fixed: Validate before using untrusted data
String validatedUsername = validateAndSanitize(
untrustedInput.getUsername()
);
Long timestamp = trustedData.getTimestamp(); // Always safe
// Fixed: Sanitized before logging
log("User " + escapeForLog(validatedUsername) + " accessed at " + timestamp);
}
// Fixed: Explicit validation boundary crossing
public TrustedUserData promoteToTrusted(UntrustedInput input)
throws ValidationException {
// All validation happens here at the trust boundary
String validUsername = validateUsername(input.getUsername());
String validEmail = validateEmail(input.getEmail());
// Only after validation, create trusted object
return new TrustedUserData(validUsername, validEmail);
}
}
// Fixed: Typed wrappers enforce trust distinction
public final class UntrustedInput {
private String username;
private String email;
private String preference;
// Setters accept any string
public void setUsername(String username) { this.username = username; }
public void setEmail(String email) { this.email = email; }
public void setPreference(String pref) { this.preference = pref; }
// Getters - caller knows this is untrusted
public String getUsername() { return username; }
public String getEmail() { return email; }
public String getPreference() { return preference; }
}
public final class TrustedUserData {
private final String username;
private final String email;
// Constructor only accepts validated data
TrustedUserData(String validatedUsername, String validatedEmail) {
this.username = validatedUsername;
this.email = validatedEmail;
}
public String getUsername() { return username; }
public String getEmail() { return email; }
}
# Fixed: Clear trust boundaries in Python
from flask import Flask, request, session
from dataclasses import dataclass
from typing import Set, Optional
import re
app = Flask(__name__)
@dataclass(frozen=True) # Immutable
class TrustedUser:
"""Represents validated, trusted user data."""
user_id: str
username: str
roles: frozenset
@dataclass
class UntrustedInput:
"""Wrapper for untrusted user input."""
value: str
source: str = "user"
def validate_username(untrusted: UntrustedInput) -> str:
"""Validation boundary - converts untrusted to trusted."""
if not untrusted.value:
raise ValueError("Username required")
if not re.match(r'^[a-zA-Z0-9_]{3,20}$', untrusted.value):
raise ValueError("Invalid username format")
return untrusted.value # Now validated
@app.route('/login', methods=['POST'])
def secure_login():
# Fixed: Wrap untrusted input explicitly
username_input = UntrustedInput(request.form.get('username', ''))
password_input = UntrustedInput(request.form.get('password', ''))
try:
# Fixed: Validate at trust boundary
validated_username = validate_username(username_input)
except ValueError as e:
return f"Invalid input: {e}", 400
# Fixed: Authenticate before storing anything
user = authenticate(validated_username, password_input.value)
if user is None:
return "Authentication failed", 401
# Fixed: Only store trusted, validated data
trusted_user = TrustedUser(
user_id=user.id,
username=user.username,
roles=frozenset(user.roles) # Roles from database
)
session['user'] = trusted_user
session['authenticated'] = True
return redirect('/dashboard')
@app.route('/admin')
def secure_admin():
# Fixed: Check typed trusted object
user = session.get('user')
if not isinstance(user, TrustedUser):
return "Not authenticated", 401
if not session.get('authenticated'):
return "Not authenticated", 401
# Fixed: Roles are from trusted source
if 'admin' in user.roles:
return render_admin_panel()
return "Access denied", 403
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:
- OWASP A04:2021 - Insecure Design
- Numerous session manipulation and privilege escalation vulnerabilities
References
- MITRE Corporation. "CWE-501: Trust Boundary Violation." https://cwe.mitre.org/data/definitions/501.html
- OWASP. "Session Management Cheat Sheet."
- CERT. "Trust Boundaries and Data Validation."