CWE-642, External Control of Critical State Data, applies when a product stores security-critical state about its users or itself in a place that unauthorized actors can reach. It affects web applications and privileged programs alike, and it is one of the oldest ideas in application security: never trust what comes back from the client.
How it happens
State is any value the program uses to decide what to do next. Who is logged in. What role they hold. Which step of a checkout they have reached. Which configuration a privileged program should use.
MITRE lists where such state tends to live: a cookie, a hidden web form field, an input parameter or argument, an environment variable, a database record, a settings file. All of them can be modified by an attacker. If the application uses the stored value to control security or resource use, then the attacker controls security or resource use.
<!-- Vulnerable: price and role live in the client -->
<input type="hidden" name="price" value="49.00">
<input type="hidden" name="role" value="user">
# Vulnerable: trusts a cookie for authorization
if request.cookies.get("role") == "admin":
show_admin_panel()
The programmer's mistake is usually an assumption. MITRE notes that some programmers assume cookies and hidden form fields cannot be modified, or forget that environment variables can be changed before a privileged program is started.
The observed examples span decades and technologies:
- A mail client stored password hashes for unrelated accounts in a hidden form field (CVE-2005-2428).
- A privileged program trusted a user-specified environment variable to modify critical configuration settings.
- A telnet daemon allowed remote clients to specify critical environment variables for the server, leading to code execution.
- An untrusted search path vulnerability through a modified
LD_LIBRARY_PATHenvironment variable.
What an attacker gains
MITRE lists three consequences:
- Bypassing a protection mechanism or gaining privileges or assuming identity, if the state relates to privilege or authentication level.
- Reading application data, because state variables may contain sensitive information the client should not know.
- Denial of service by crash, exit or restart, since modified state can violate the application's expectations and cause an unexpected error.
The first is the one that makes the news, but the second matters too. A hidden field holding hashes or internal identifiers is a disclosure even if no one edits it.
How to find it
The record lists three detection methods:
- Automated static analysis (SAST) can find some instances by analyzing source code without executing it.
- Fuzzing, using dynamic tools with large test suites of diverse inputs. The product may slow down, but it should not become unstable, crash or generate incorrect results.
- Manual dynamic analysis, such as penetration testing, threat modeling and interactive tools that let a tester record and modify an active session. MITRE notes these can be more effective than strictly automated techniques.
A simple manual check: use your browser's developer tools or an intercepting proxy to edit every cookie, hidden field and parameter in a session, and see what changes. For privileged programs, run them with unusual environment variables and see which settings follow.
Click through it at your own pace, or open it full screen.
How to fix it
MITRE's mitigations at the design stage:
- Understand all the places an attacker can reach. Do not assume cookies and hidden fields are safe, and remember that environment variables can be modified before a privileged program runs.
- Store state information and sensitive data on the server side only. Have the system keep definitive track of its own state and user state, with rules for legitimate state transitions. Do not let any user affect state directly except through legitimate actions that lead to those transitions.
- If information must be stored on the client, do not do so without encryption and integrity checking, or without a server-side mechanism to catch tampering.
- Use a message authentication code such as HMAC against the state or sensitive data that has to be exposed, to guarantee it has not been modified. Use a strong hash function.
# Server-side session: the client holds only an opaque ID
session = store.get(request.cookies["sid"])
if session and session["role"] == "admin":
show_admin_panel()
# If state must travel, sign it and verify before use
import hmac, hashlib
def sign(value, key):
return hmac.new(key, value.encode(), hashlib.sha256).hexdigest()
def verify(value, tag, key):
return hmac.compare_digest(sign(value, key), tag)
Prices and totals should be recomputed on the server from product IDs, not read from the form. Privileged programs should set their own environment instead of inheriting it.
Quick check
Are hidden form fields safe for storing state?
No. Hidden only means not displayed. The user can still read and change the value.
If state must live on the client, what is required?
Encryption and integrity checking, or a server-side mechanism that catches tampering, for example an HMAC over the data.
Why do environment variables count as state?
They can be modified before a privileged program starts, and the program may trust them to set critical configuration.