CWE-472: External Control of Assumed-Immutable Web Parameter
Overview
Assumed-immutable parameter vulnerabilities occur when applications trust that client-controlled data (hidden form fields, cookies, disabled inputs, URL parameters) remains unchanged, failing to validate on server-side, enabling price manipulation, privilege escalation, and business logic bypass.
OWASP Classification
A06:2025 - Insecure Design
Risk
High: Trusting client-side "immutable" data enables price manipulation (hidden fields), privilege escalation (user role cookies), discount/coupon abuse, quantity limits bypass, shipping cost manipulation, and complete business logic compromise.
Remediation Steps
Core principle: Do not treat client-supplied parameters as immutable; recompute and verify server-side for every request.
Locate the Assumed-Immutable Parameter Vulnerability
When reviewing security scan results:
- Examine data_paths: Identify where client-controlled data is trusted without server-side validation
- Find assumed-immutable fields: Hidden form fields, disabled inputs, cookies, URL parameters
- Trace parameter usage: Where are these values used - pricing, authorization, business logic
- Check for validation: Look for missing server-side validation of "read-only" client data
- Assess impact: Can users modify values to bypass limits, change prices, escalate privileges
Never Trust Client-Side Data (Primary Defense)
# WRONG - trusting hidden field
@app.route('/checkout', methods=['POST'])
def checkout():
price = float(request.form['price']) # User can modify!
quantity = int(request.form['quantity'])
total = price * quantity # Manipulated price!
# RIGHT - lookup server-side
@app.route('/checkout', methods=['POST'])
def checkout():
product_id = request.form['product_id']
product = db.get_product(product_id)
price = product.price # Server-side source of truth
quantity = int(request.form['quantity'])
total = price * quantity
Why this works: All security-sensitive data must originate from and be validated on the server. Client-provided data can always be modified using browser dev tools, proxy tools, or direct HTTP requests.
Key principle: Use client data only as identifiers (IDs), never as trusted values (prices, roles, limits). Look up the actual values server-side based on the identifier.
Store Sensitive Data Server-Side Only
// WRONG - using cookies for authorization
// Cookie: role=admin ← User can modify in browser
String role = request.getCookies()["role"];
if ("admin".equals(role)) { /* ... */ }
// RIGHT - use server-side session
HttpSession session = request.getSession();
String role = (String) session.getAttribute("role");
// BEST - lookup from database
User user = userService.getCurrentUser();
if (!user.hasRole("ADMIN")) {
throw new AccessDeniedException();
}
What to store server-side:
- User roles and permissions (in session or database)
- Pricing information (in database, looked up by product ID)
- Discount percentages (in user profile or promotion table)
- Account balances and limits
- Any data that affects authorization or business logic
Validate All Parameters Against Business Rules
# Even "read-only" fields must be validated
discount = float(request.form['discount'])
# Validate range
if discount < 0 or discount > 0.20: # Max 20%
raise ValueError("Invalid discount")
# Verify against user's actual entitlements
user = get_current_user()
if discount > user.max_allowed_discount:
raise ValueError("Discount exceeds user limit")
# Validate quantity limits
quantity = int(request.form['quantity'])
if quantity < 1 or quantity > product.max_per_order:
raise ValueError("Invalid quantity")
Validation rules:
- Range checks (min/max values)
- Business rule verification (user entitlements, order limits)
- Data type validation
- Cross-field validation (total = price × quantity)
Use Signed Tokens for Client State When Necessary
When you must pass sensitive data to client:
import hmac
import hashlib
SECRET_KEY = b'your-secret-key' # From secure config
# Generate signed price token
def sign_price(price):
signature = hmac.new(SECRET_KEY, str(price).encode(), hashlib.sha256).hexdigest()
return f"{price}|{signature}"
# Verify on submission
def verify_price(token):
try:
price_str, signature = token.split('|')
expected = hmac.new(SECRET_KEY, price_str.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
raise ValueError("Tampered price detected")
return float(price_str)
except (ValueError, AttributeError):
raise ValueError("Invalid price token")
When to use signed tokens:
- Shopping cart state passed between pages
- Temporary authorization grants
- Workflow state in multi-step processes
- Always prefer server-side sessions when possible
Monitor and Test for Parameter Tampering
Testing strategies:
- Use browser dev tools to modify hidden fields before submission
- Use proxy tools (Burp Suite, OWASP ZAP) to intercept and modify requests
- Test modifying disabled form fields (enable with JavaScript, then change)
- Test cookie modification: change role, permissions, user ID
- Test URL parameter tampering: change prices, quantities, discounts
- Test negative values, zero, extremely large values
Browser-based tests:
// In browser console - modify hidden field
document.querySelector('[name=price]').value = '0.01';
// Enable disabled field
document.querySelector('[name=discount]').disabled = false;
document.querySelector('[name=discount]').value = '100';
// Modify cookie
document.cookie = 'role=admin; path=/';
Monitoring:
- Log parameter validation failures
- Alert on suspicious patterns (prices set to $0.01, discount = 100%)
- Track HMAC signature verification failures
- Monitor for repeated tampering attempts from same user/IP
- Review business metrics for anomalies (all orders $0.01)
Verification steps:
- Modify all hidden fields and verify rejection
- Change cookie values and verify they're ignored
- Test with proxy tool to modify requests in flight
Common Vulnerable Patterns
<!-- Hidden field with price -->
<input type="hidden" name="price" value="99.99">
<input type="text" name="quantity">
<!-- Disabled field (can be enabled client-side) -->
<input type="text" name="discount" value="0" disabled>
<!-- Cookie with role -->
Set-Cookie: isAdmin=true
<!-- URL parameter -->
/purchase?item=123&price=99.99
Attack Examples
// Modify hidden field
document.querySelector('[name=price]').value = '0.01';
// Enable disabled field
document.querySelector('[name=discount]').disabled = false;
document.querySelector('[name=discount]').value = '100';
// Modify cookie
document.cookie = 'role=admin';