Modification of Assumed-Immutable Data (MAID)
Description
Modification of Assumed-Immutable Data is a vulnerability where the product does not properly protect an assumed-immutable element from being modified by an attacker. This occurs when critical inputs that the program assumes will remain constant can actually be changed. Common vulnerable resources include hidden form fields in web applications, cookies, environment variables, and data retrieved from reverse DNS lookups. Attackers can modify these values to bypass security controls or alter application behavior.
Risk
MAID vulnerabilities allow attackers to manipulate data the application trusts. Hidden form fields containing prices or user roles can be modified to obtain unauthorized discounts or privileges. Cookies storing authentication state can be tampered with to impersonate other users. Environment variables trusted for configuration can be manipulated to change application behavior. The risk is particularly severe when the assumed-immutable data controls security decisions like authentication, authorization, or pricing.
Solution
Implement integrity checks during storage or transmission through untrusted sources. Store sensitive data in trusted locations protected from external influence (server-side sessions instead of client-side cookies). Use cryptographic signatures (HMAC) to detect tampering with data that must traverse untrusted channels. Never trust client-side validation alone - always validate on the server. Avoid using environment variables or external data for security-critical decisions without verification. Treat all data from external sources as potentially modified.
Common Consequences
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Modify Application Data - Common data types that are attacked are environment variables, web application parameters, and HTTP headers. |
| Integrity | Scope: Integrity Unexpected State - Attacker can alter application state through modification of data the application assumes is constant. |
Example Code
Vulnerable Code
<!-- Vulnerable: Hidden form fields that can be modified -->
<form action="/checkout" method="POST">
<!-- Vulnerable: Price can be modified by user -->
<input type="hidden" name="product_id" value="123">
<input type="hidden" name="price" value="99.99">
<input type="hidden" name="discount" value="0">
<!-- Vulnerable: User role in form -->
<input type="hidden" name="user_role" value="customer">
<input type="submit" value="Purchase">
</form>
<!-- Attacker modifies DOM or intercepts request:
price=0.01, discount=99, user_role=admin -->
# Vulnerable: Trusting client-provided data
from flask import Flask, request, session
app = Flask(__name__)
@app.route('/checkout', methods=['POST'])
def vulnerable_checkout():
# Vulnerable: Price from form (can be modified)
price = float(request.form.get('price'))
quantity = int(request.form.get('quantity'))
total = price * quantity
# Attacker submitted price=0.01
process_payment(total)
@app.route('/admin/action', methods=['POST'])
def vulnerable_admin_action():
# Vulnerable: Role from cookie (can be modified)
user_role = request.cookies.get('role', 'guest')
# Attacker sets cookie: role=admin
if user_role == 'admin':
perform_admin_action() # Unauthorized access
@app.route('/api/data')
def vulnerable_api():
# Vulnerable: Trusting client IP from header (can be spoofed)
client_ip = request.headers.get('X-Forwarded-For',
request.remote_addr)
if is_internal_ip(client_ip):
# Attacker adds header: X-Forwarded-For: 10.0.0.1
return get_sensitive_data()
// Vulnerable: Trusting mutable array returned by method
public class VulnerablePermissions {
private String[] adminUsers = {"admin", "root", "superuser"};
// Vulnerable: Returns reference to internal array
public String[] getAdminUsers() {
return adminUsers; // Caller can modify!
}
public boolean isAdmin(String username) {
for (String admin : adminUsers) {
if (admin.equals(username)) {
return true;
}
}
return false;
}
}
// Attack:
// String[] admins = permissions.getAdminUsers();
// admins[0] = "attacker"; // Now "attacker" is admin!
<?php
// Vulnerable: PHP register_globals style vulnerability
function vulnerable_authenticate() {
// Vulnerable: $authenticated might come from user input
// if register_globals is enabled or manual extraction happens
if (isset($_GET['authenticated'])) {
$authenticated = $_GET['authenticated']; // Attacker controls!
}
if ($authenticated) {
// Attacker passes ?authenticated=1
grant_access();
}
}
// Vulnerable: Trusting PHP_SELF
function vulnerable_form() {
// Vulnerable: PHP_SELF can be manipulated
echo '<form action="' . $_SERVER['PHP_SELF'] . '">';
// Attacker visits: /page.php/"><script>alert(1)</script>
// Results in XSS via "immutable" server variable
}
?>
Fixed Code
# Fixed: Server-side data validation
from flask import Flask, request, session
from functools import wraps
import hmac
import hashlib
app = Flask(__name__)
app.secret_key = 'your-secret-key'
def get_product_price(product_id):
"""Get price from server-side database."""
return database.query("SELECT price FROM products WHERE id = ?", product_id)
@app.route('/checkout', methods=['POST'])
def secure_checkout():
product_id = request.form.get('product_id')
quantity = int(request.form.get('quantity'))
# Fixed: Get price from server, not client
price = get_product_price(product_id)
if price is None:
return "Invalid product", 400
total = price * quantity
process_payment(total)
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
# Fixed: Get role from server-side session, not cookie
if 'user_id' not in session:
return "Unauthorized", 401
user = database.get_user(session['user_id'])
request.user = user
return f(*args, **kwargs)
return decorated
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
# Fixed: Verify role from database
if not hasattr(request, 'user') or request.user.role != 'admin':
return "Forbidden", 403
return f(*args, **kwargs)
return decorated
@app.route('/admin/action', methods=['POST'])
@login_required
@admin_required
def secure_admin_action():
# Role verified from server-side session and database
perform_admin_action()
@app.route('/api/data')
def secure_api():
# Fixed: Don't trust X-Forwarded-For directly
# Use trusted proxy configuration or verify differently
# Option 1: Use actual remote address from trusted proxy
client_ip = get_trusted_client_ip(request)
# Option 2: Use authentication instead of IP
api_key = request.headers.get('X-API-Key')
if not verify_api_key(api_key):
return "Unauthorized", 401
return get_sensitive_data()
// Fixed: Return defensive copy of array
public class SecurePermissions {
private final String[] adminUsers = {"admin", "root", "superuser"};
// Fixed: Return copy of array
public String[] getAdminUsers() {
return Arrays.copyOf(adminUsers, adminUsers.length);
}
// Fixed: Or return unmodifiable view
public List<String> getAdminUsersList() {
return Collections.unmodifiableList(Arrays.asList(adminUsers));
}
// Fixed: Better - don't expose internal data at all
public boolean isAdmin(String username) {
for (String admin : adminUsers) {
if (admin.equals(username)) {
return true;
}
}
return false;
}
}
<?php
// Fixed: Proper authentication without trusting user input
function secure_authenticate() {
session_start();
// Fixed: Check session, not user-controllable variables
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) {
grant_access();
} else {
require_login();
}
}
// Fixed: Don't use PHP_SELF, use known action
function secure_form() {
// Fixed: Use explicit, known action URL
$action = '/process_form.php';
echo '<form action="' . htmlspecialchars($action, ENT_QUOTES) . '">';
}
// Fixed: Sign data that must traverse client
function create_signed_form_data($data) {
$json = json_encode($data);
$signature = hash_hmac('sha256', $json, SECRET_KEY);
return base64_encode($json . '|' . $signature);
}
function verify_signed_form_data($encoded) {
$decoded = base64_decode($encoded);
list($json, $signature) = explode('|', $decoded, 2);
// Fixed: Verify signature
$expected = hash_hmac('sha256', $json, SECRET_KEY);
if (!hash_equals($expected, $signature)) {
throw new SecurityException("Data tampered");
}
return json_decode($json, true);
}
?>
<!-- Fixed: Signed hidden fields -->
<form action="/checkout" method="POST">
<!-- Product info signed by server -->
<input type="hidden" name="signed_data"
value="eyJwcm9kdWN0X2lkIjoxMjMsInByaWNlIjo5OS45OX0=|abc123signature">
<input type="text" name="quantity" value="1">
<input type="submit" value="Purchase">
</form>
<!-- Server verifies signature before processing -->
CVE Examples
- CVE-2002-1757 - Authentication relied on PHP_SELF variable that could be manipulated by attackers.
- CVE-2005-1905 - Privilege escalation through modification of code addresses assumed to be immutable in drivers.
References
- MITRE Corporation. "CWE-471: Modification of Assumed-Immutable Data (MAID)." https://cwe.mitre.org/data/definitions/471.html
- OWASP. "Parameter Tampering." https://owasp.org/www-community/attacks/Parameter_Tampering
- CAPEC-384. "Application API Message Manipulation via Man-in-the-Middle."