CWE-501: Trust Boundary Violation
Overview
Trust boundary violations occur when untrusted data (user input, HTTP request data) is stored in trusted context (session, internal objects) without validation, or when trusted data is exposed to untrusted context, enabling session poisoning, privilege escalation, and security control bypass.
OWASP Classification
A06:2025 - Insecure Design
Risk
High: Trust violations enable session poisoning (storing attacker-controlled data in session), privilege escalation (manipulating stored roles), cache poisoning, bypassing security checks, and violating security assumptions throughout application.
Remediation Steps
Core principle: Treat trust boundaries explicitly; never let data cross boundaries without validation, authorization, and least privilege.
Locate Trust Boundary Violations
When reviewing security scan results:
- Examine data_paths: Identify where untrusted data crosses into trusted context
- Find trust boundaries: Session storage, caches, internal objects, security contexts
- Check for validation: Look for missing validation before storing in trusted contexts
- Identify trust assumptions: Code that assumes session/cache data is safe
- Assess impact: Can attacker control session variables, roles, or security decisions
Common trust boundaries:
- HTTP request → Session storage
- User input → Application cache
- External data → Internal security context
- Untrusted parameters → Authorization decisions
Validate Before Crossing Trust Boundary (Primary Defense)
# WRONG - storing untrusted data directly in session
@app.route('/set_preference')
def set_pref():
pref = request.args.get('pref')
session['user_pref'] = pref # Trust violation!
# Attacker can set: ?pref=<script>alert(1)</script>
# CORRECT - validate before storing in trusted context
ALLOWED_PREFS = ['light', 'dark', 'auto']
@app.route('/set_preference')
def set_pref():
pref = request.args.get('pref')
# Validate against allowlist
if pref not in ALLOWED_PREFS:
abort(400, "Invalid preference")
# Now safe to store in session
session['user_pref'] = pref # Trusted after validation
Why this works: Validating data before crossing the trust boundary ensures only known-good values enter trusted contexts. Allowlisting is the strongest validation approach.
Validation strategies:
- Use allowlists for enumerated values
- Type validation and conversion
- Length and format checks
- Sanitization for display data
- Never trust user input implicitly
Never Store Untrusted Data in Session Without Validation
// WRONG - session poisoning vulnerability
HttpSession session = request.getSession();
String userInput = request.getParameter("data");
session.setAttribute("userData", userInput); // Violation!
String role = request.getParameter("role");
session.setAttribute("role", role); // Privilege escalation!
// CORRECT - only store validated/sanitized data
String userInput = request.getParameter("data");
// Validate and sanitize
String sanitized = SecurityUtils.sanitizeInput(userInput);
if (!SecurityUtils.isValid(sanitized, MAX_LENGTH)) {
throw new ValidationException("Invalid input");
}
// Safe to store after validation
session.setAttribute("userData", sanitized);
// For security-critical data, NEVER accept from user
// Get role from database based on authenticated user ID
User user = userService.getAuthenticatedUser();
session.setAttribute("role", user.getRole()); // From trusted source
Session security rules:
- Never store user-provided roles/permissions
- Validate all user input before session storage
- Separate display preferences from security decisions
- Re-validate session data when making security decisions
Separate Trusted and Untrusted Data Structures
class UserContext:
def __init__(self, user_id, role):
# TRUSTED - from authentication/database
self._user_id = user_id # Private, not settable
self._role = role # Private, not settable
self._authenticated_at = datetime.now()
# UNTRUSTED - from user, kept separate
self.preferences = {} # User preferences
self.display_settings = {} # Display settings
@property
def user_id(self):
return self._user_id # Read-only access
@property
def role(self):
return self._role # Read-only access
def set_preference(self, key, value):
# Validate untrusted data before storing
if key not in ALLOWED_PREFERENCE_KEYS:
raise ValueError(f"Invalid preference key: {key}")
if not self._validate_preference_value(key, value):
raise ValueError(f"Invalid value for {key}")
self.preferences[key] = value
def _validate_preference_value(self, key, value):
# Type and range validation
if key == 'theme':
return value in ['light', 'dark', 'auto']
elif key == 'language':
return value in SUPPORTED_LANGUAGES
return False
Separation principles:
- Use private fields for trusted data
- Provide read-only access to sensitive fields
- Keep untrusted data in separate containers
- Apply validation at the boundary
Validate When Reading from Trusted Contexts
# Don't blindly trust session data - it could be poisoned
def get_user_role():
role = session.get('role')
# Re-validate even from session (defense in depth)
VALID_ROLES = ['user', 'admin', 'moderator']
if role not in VALID_ROLES:
# Session might be poisoned or corrupted
logger.warning(f"Invalid role in session: {role}")
clear_session()
raise SecurityException("Invalid session data")
return role
# Better: lookup from database, don't rely on session
def get_user_role_secure():
user_id = session.get('user_id')
if not user_id:
raise AuthenticationException("Not authenticated")
# Authoritative source from database
user = db.query(User).get(user_id)
if not user:
raise AuthenticationException("User not found")
return user.role
Defense-in-depth validation:
- Don't assume session data is safe
- Re-validate before security decisions
- Use database as authoritative source for critical data
- Clear session if data is invalid
Monitor and Test for Trust Boundary Violations
Testing strategies:
# Test session poisoning
def test_cannot_set_admin_role():
# Attempt to poison session with admin role
response = client.post('/set_preference',
data={'pref': 'admin_role'})
# Should reject invalid preference
assert response.status_code == 400
# Session should not contain attacker-controlled role
with client.session_transaction() as sess:
assert 'admin_role' not in sess
assert sess.get('role') != 'admin'
# Test cache poisoning
def test_cache_validation():
# Try to inject malicious data into cache
malicious_data = '<script>alert(1)</script>'
response = client.post('/update', data={'value': malicious_data})
# Verify data is sanitized before caching
cached = cache.get('user_data')
assert '<script>' not in cached
Attack simulation:
- Modify URL parameters to inject admin roles
- Tamper with form data before submission
- Use proxy to modify requests (Burp Suite)
- Test with XSS payloads in session data
- Attempt privilege escalation via session manipulation
Monitoring:
- Log validation failures at trust boundaries
- Alert on invalid session data detected
- Monitor for session/cache poisoning attempts
- Track unexpected role changes
- Audit security-critical session operations
Common Violation Patterns
# Storing unvalidated input in session
session['redirect_url'] = request.args.get('next') # Open redirect!
session['lang'] = request.args.get('lang') # Could be malicious
# Storing attacker-controlled data
user_data = request.json
cache.set(f'user_{user_id}', user_data) # Cache poisoning!
# Mixing trust levels
class User:
def __init__(self):
self.id = None # Trusted (from DB)
self.display_name = None # Untrusted (user input)
# Dangerous - mixed trust levels
Attack Scenarios
# Session poisoning
# Attacker sets: ?role=admin
session['role'] = request.args.get('role') # Violation!
# Later
if session['role'] == 'admin': # Attacker is now admin!
allow_admin_action()
# Cache poisoning
user_input = request.args.get('data')
cache.set(key, user_input) # Untrusted data in cache
# Other users get attacker's data
# Object injection
session['user_object'] = pickle.loads(request.data) # RCE!
Security Checklist
- All user input validated before session storage
- Security-critical data (roles, permissions) from database, not user
- Trusted and untrusted data kept separate
- Session data re-validated before security decisions
- Allowlists used for enumerated values
- Input sanitized for display contexts