Any value an application hands to the client and later reads back, whether a cookie, a URL token, a hidden form field or a mobile app data file, is attacker-controlled input, and encrypting or encoding it does not change that. MITRE CAPEC-39 Manipulating Opaque Client-based Data Tokens describes an attacker modifying client-held tokens that the application later reinterprets as authentication data or business data, such as prices or wallet balances, and undermining the assumption that encryption or obfuscation protects them from tampering. CAPEC rates it a Standard-level pattern with High likelihood and Medium typical severity, a child of CAPEC-22 Exploiting Trust in Client and parent of CAPEC-31 Accessing/Intercepting/Modifying HTTP Cookies. This guide explains why opaque is not the same as protected, which design and cryptographic choices prevent the attack, how to test and detect it, and how to roll out fixes without logging every user out. It is for application security engineers, developers and penetration testers. For the CAPEC catalogue itself see the MITRE CAPEC Guide; cookie attributes are covered in the Cookie Security Guide.
Key takeaways
- Opaque is not tamper-proof. CAPEC's own example shows an ECB-encrypted cookie whose access-level byte was changed by flipping ciphertext bits, without the key or the algorithm.
- Integrity needs a MAC or signature, checked first. CAPEC's mitigations call for integrity protection (a keyed MAC or signed hash), and OWASP ASVS 5.0 V9.1.1 requires self-contained tokens to be validated by signature or MAC before their contents are accepted.
- Integrity does not make a value correct. CAPEC warns that if the client controls the whole process of generating a value, protecting its integrity does not help; prices, roles and balances belong on the server.
- The signing key is the whole control. Microsoft reported more than 3,000 publicly disclosed ASP.NET machine keys usable for ViewState code injection; a leaked or copied key turns every signed token into a forgeable one.
- Prefer reference tokens for anything that grants authority. A random identifier pointing at server-side state cannot be edited into a different state.
How the pattern works
CAPEC-39 has two prerequisites: the attacker already has some access, or can steal another user's client token, and the data is held client-side in a way that can be changed without detection because it carries no integrity check of its own or in a separate server-side store. CAPEC's execution flow has two phases, described here at the level a defender needs:
- Explore: the attacker finds which client-held parameters drive business or security decisions (cookies, URL tokens, hidden fields, decompiled client data) and works out what protects them, looking for familiar encodings, delimiters and signatures.
- Experiment: the attacker changes one value at a time, logically, arithmetically, bitwise or structurally, and watches how the server's responses change.
CAPEC's examples show the range of impact. A price comparison site passed encrypted pricing data in URLs to fulfilling vendors, and opaque changes produced different prices. An authentication cookie encrypted in ECB mode carried a user-level flag that could be swapped for an administrator flag by trial-and-error bit flipping. An older blog application granted administrator access when a cookie was simply set to 1 (CVE-2006-0944). CAPEC lists the consequences as gained privileges and modified data.
Where client-held state hides
| Carrier | Typical content | Typical mistake |
|---|---|---|
| Cookies | User ID, role, preferences, basket, discount | Plain or encoded values trusted for authorisation (CWE-565 Reliance on Cookies without Validation and Integrity Checking) |
| URL and query tokens | Order references, prices, redirect targets, partner hand-off data | Encrypted without a MAC, so ciphertext changes still decrypt to usable values |
| Hidden form fields and framework state | ASP.NET ViewState, wizard step, item price, account ID | State integrity relies on a machine or signing key that is default, shared or published |
| Self-contained tokens | JWT claims, signed session cookies | Signature not verified, algorithm not pinned, or claims trusted after the user's rights changed |
| Mobile and desktop client files | Local entitlement flags, licence state, game currency | Server accepts client-reported balances or entitlements |
| Browser storage | localStorage and IndexedDB caches of user profile and permissions | Front end decides what the user may do, back end does not re-check |
Design choices that prevent it
The strongest fix is architectural: do not send authority to the client at all. Where state must travel, protect it cryptographically and still re-check it.
| Approach | What it protects | When to use | Trade-off |
|---|---|---|---|
| Reference token (random ID, state on server) | Everything: the client never sees the state | Sessions, baskets, prices, roles, multi-step flows | Server-side storage and lookups; sticky or shared session store |
| Signed value (HMAC or digital signature) | Integrity only; the content stays readable | Non-secret values that must survive a stateless hop, such as a user ID or a short-lived link | Key management; replay of a valid old value unless it expires |
| Authenticated encryption (AES-GCM or similar, or encrypt-then-MAC) | Confidentiality and integrity | Values the user must neither read nor change | Key management; larger tokens; still replayable |
| Encryption without authentication (ECB, bare CBC or CTR) | Confidentiality only, and ECB poorly | Never for client-held state | Bit flipping and block shuffling change the plaintext undetected |
| Encoding or obfuscation (Base64, hex, custom) | Nothing | Never as a control | CAPEC rates manipulating an obfuscated token as medium skill (an encrypted one as high) |
The OWASP Cryptographic Storage Cheat Sheet says authenticated modes such as GCM or CCM should always be used where available, that CTR or CBC need separate authentication such as encrypt-then-MAC, and that ECB should not be used outside very specific circumstances. ASVS 5.0 turns those into verifiable requirements: V11.3.1 (no ECB), V11.3.3 (authenticated encryption or encryption plus MAC) and V11.3.5 (encrypt-then-MAC order).
Framework features to use
Most web frameworks provide the primitives, so the work is mostly choosing the right one and managing its key. Frameworks are listed alphabetically as examples.
- ASP.NET Web Forms: ViewState integrity relies on the ValidationKey, which produces a MAC attached to the ViewState, with the DecryptionKey for optional encryption. Microsoft advises not copying keys from public sources, rotating keys regularly and encrypting the
machineKeysection ofweb.configat deployment. - Django:
django.core.signingprovidesSignerandTimestampSigner(withmax_ageto expire values), asaltto separate signing namespaces, andSECRET_KEY_FALLBACKSfor key rotation. Signed data is tamper-evident but readable. - Ruby on Rails:
cookies.signedprevents users tampering with a value but leaves it readable;cookies.encryptedprevents both reading and tampering.
Whichever primitive you use, follow CWE-565's advice: validate the value on the server before any security decision, add integrity checks, and limit replay with timeouts and an unpredictable server-side value in the integrity check.
Server-side validation rules
CAPEC's mitigation list ends with the rule that matters most: validate on the server that client tokens are consistent with what is expected. In practice:
- Recompute, do not accept: prices, discounts, totals, tax and shipping are recalculated from the catalogue and the user's entitlements at checkout. The client sends item IDs and quantities only. See the Business Logic Vulnerabilities Guide.
- Authorise from the server's view of the subject: roles and tenant membership come from the session store or directory, never from a client flag. ASVS V8.3.1 requires authorisation at a trusted service layer, not in controls an untrusted consumer could manipulate.
- Bind tokens to context: include the user, purpose and expiry inside the signed or encrypted payload, and reject a token used by another user, for another purpose or after expiry.
- Verify before parsing: check the MAC or signature first, then deserialise. ASVS V9.1.1 requires this for self-contained tokens; Django's preference for JSON over pickle means a stolen key cannot be used to execute commands through the serialisation format.
- Keep sensitive data out of client storage: ASVS V14.3.3 limits browser storage to session tokens, not sensitive data.
- Fail closed and log: treat a bad signature as a security event, return a generic error and record it.
Testing for it
- Inventory first: list every value the application sends to the client and reads back, from proxy traffic, templates and mobile client storage. Mark each one that influences a security or money decision.
- Tamper one byte: in an authorised test, change a single byte of each opaque value. If the request still succeeds with different behaviour, the value has no integrity check.
- Swap between users: replay one user's token in another user's session to test context binding.
- Replay after change: reuse a token issued before a role downgrade or logout. ASVS V8.3.2 expects changes to authorisation values to apply immediately, or mitigating controls where self-contained tokens make that impossible.
- Review the code path: confirm which framework primitive is used, whether a MAC is checked before decoding, and where the key lives. See the OWASP ASVS Guide for mapping findings.
Detection and measurement
Integrity failures are a high-quality signal because legitimate users rarely produce them.
| Signal | Where to log | What it suggests |
|---|---|---|
Signature or MAC validation failures (for example Django BadSignature, ViewState MAC errors) | Application security log | Tampering or a key mismatch after deployment; a burst from one client is probing |
Expired-token rejections (for example SignatureExpired) | Application log | Replay of captured tokens |
| Recomputed price differs from client-submitted price | Checkout service | Price manipulation attempts against an older client contract |
| Token used from a different user or session context | Session service | Stolen or swapped token |
| Publicly disclosed machine or signing key detected | Endpoint and code scanning; Microsoft Defender for Endpoint raises "Publicly disclosed ASP.NET machine key" | Every token signed with that key is forgeable |
Metrics we suggest tracking:
- Share of client-held values that influence security or money decisions and are server-recomputed or reference tokens (target 100 per cent).
- Share of remaining self-contained tokens verified by MAC or signature before parsing (target 100 per cent).
- Signing keys with a named owner, vault storage and a rotation date (target 100 per cent); keys found in source code (target zero).
- Integrity failure rate per thousand requests, with alerting on bursts per client.
- Time to rotate a signing key end to end, measured in a drill.
Rolling out fixes
- Classify each value: move it server-side, sign it, or encrypt it with authentication.
- Introduce the new format alongside the old: accept both for one session lifetime, issue only the new format, and log every old-format token you still accept.
- Rotate keys with fallbacks: use the framework's fallback mechanism (for example
SECRET_KEY_FALLBACKS) so existing tokens still verify while new ones use the new key. - Remove the old path: once old-format acceptances reach zero, delete the legacy parser and the old key.
- Rollback: re-enable acceptance of the old format behind a feature flag; never roll back by turning off verification.
Where identity fits
The keys that sign and encrypt client tokens are non-human credentials with very high impact: whoever holds them can mint any session or price token the application will accept. Give each key an owner, keep it in a vault or KMS rather than a configuration file, scan repositories for it, and rotate on a schedule and on any exposure. Microsoft notes that if a disclosed key has already been exploited, rotating it does not remove backdoors, so plan a full investigation. See the Cryptographic Key Management Guide and, for token-specific pitfalls, the JWT Security Guide.
Common failure patterns
- Encrypt and forget: encrypting a cookie with AES in ECB or bare CBC mode and assuming nobody can change it.
- Trusting the price field: charging the amount the browser posts rather than recomputing it.
- Copied or default keys: machine keys and secret keys pasted from samples, tutorials or another environment.
- Parse before verify: deserialising a token, sometimes with a dangerous serialiser, before checking its signature.
- Front-end authorisation: hiding admin buttons based on a client flag while the API accepts the call from anyone.
- No expiry: signed tokens valid forever, so a captured token stays useful after the user's rights change.
Framework mapping
| Framework | Reference | How it applies |
|---|---|---|
| MITRE CAPEC | CAPEC-39; parent CAPEC-22; child CAPEC-31 | The pattern, the trust failure behind it and the cookie-specific variant |
| MITRE CWE | CWE-565 (plus CWE-353, CWE-472 and others listed by CAPEC) | Underlying weaknesses: cookie reliance, missing integrity check, external control of assumed-immutable parameters |
| OWASP ASVS 5.0 | V2.2.2, V8.3.1, V8.3.2, V9.1.1, V11.3.1, V11.3.3, V11.3.5, V14.3.3 | Server-side validation and authorisation, token verification, authenticated encryption, client storage |
| NIST SP 800-53 Rev. 5 | SC-23 Session Authenticity, SI-10 Information Input Validation, SC-13 Cryptographic Protection, AC-3 Access Enforcement | Session integrity, validating client input, approved cryptography, server-side enforcement |
| NIST CSF 2.0 | PR.DS-02 | Integrity of data in transit between client and server |
Practitioner checklist
- Inventory every value the client holds and returns, and mark those that drive security or money decisions.
- Move roles, prices, balances and workflow state to the server behind reference tokens.
- Protect remaining client-held values with a MAC, a signature or authenticated encryption; remove ECB and unauthenticated modes.
- Verify integrity before parsing, and bind each token to user, purpose and expiry.
- Recompute prices and re-check authorisation on every sensitive request.
- Store signing and machine keys in a vault, scan code for them, and rotate with fallbacks.
- Log and alert on integrity and expiry failures per client.
- Add one-byte tamper, cross-user swap and replay-after-downgrade tests to every release test plan.
Standards and references
- MITRE CAPEC: Manipulating Opaque Client-based Data Tokens (CAPEC-39)
- MITRE CWE: CWE-565 Reliance on Cookies without Validation and Integrity Checking
- OWASP: Application Security Verification Standard 5.0
- OWASP: Cryptographic Storage Cheat Sheet
- Microsoft Security Blog: Code injection attacks using publicly disclosed ASP.NET machine keys
- Django: Cryptographic signing
- Ruby on Rails: ActionDispatch::Cookies
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: MITRE CAPEC Guide · Session Hijacking and Cookie Theft Guide · Broken Access Control Prevention Guide · Cryptographic Protocol Failures and Misuse Guide