The idea: trusting what nobody checked
Software or Data Integrity Failures is item A08 of the OWASP Top 10:2025. It covers code and data that an application trusts without checking that they are unchanged and come from where it thinks. Anything that crosses a trust boundary can have been changed on the way. That includes a cookie, a token or a hidden form field coming back from the browser, a script from a CDN, a software update, a package or a build artifact. A check only counts if the other side cannot fake it.
Typical weaknesses in this category (CWE):
- CWE-345 Insufficient Verification of Data Authenticity
- CWE-353 Missing Support for Integrity Check
- CWE-494 Download of Code Without Integrity Check
- CWE-502 Deserialization of Untrusted Data
- CWE-565 Reliance on Cookies without Validation and Integrity Checking
- CWE-829 Inclusion of Functionality from Untrusted Control Sphere
- CWE-915 Improperly Controlled Modification of Dynamically-Determined Object Attributes
Reading the canvas: the client region is the browser, mallory's (the attacker, an ordinary customer) or alice's (a victim). The server side holds the shop.example app with its secret key K_shop, its checks and its log, and cdn.example. The panel below shows the latest request or response as it travels. Pick an attack and an app (vulnerable or fixed) and press Send, or run a demo. Each demo runs the same attack twice, first against the vulnerable app and then against the fixed one. Signatures and hashes on the canvas are shortened, made-up values.
Client-side state and HMAC
The vulnerable app keeps the user's role in a cookie, cart=base64({"user":"mallory","role":"user"}). Base64 is an encoding, not protection. mallory decodes it, changes role to admin, encodes it again and gets the admin panel (Demo 1: edited cookie).
The fixed app appends a message authentication code: cart=payload.sig with sig = HMAC-SHA256(K_shop, payload). On every request it recomputes the HMAC and compares the two in constant time before it parses the payload. mallory can still edit the payload, but she cannot compute the matching sig without K_shop, so the request ends in 400 Bad Request. An unchanged cookie still passes (Demo 4: an honest cookie still works).
Often the better fix is to keep the state on the server altogether: the cookie holds only a random session id, and the role lives in the session store. A signature is for the cases where the client really must carry the data.
Signed tokens and the alg header
A JSON Web Token is header.payload.signature. The header says which algorithm signed it. If the verifier believes that header, the attacker chooses how her own token is checked. With "alg":"none" there is nothing to check, and a careless library reports the token as valid (Demo 2: JWT alg none). A related mistake is algorithm confusion: a server that expects RS256 is given an HS256 token, and the library uses the public key as the HMAC secret.
The fix is to pin the algorithm and the key in the server's code, for example jwt.verify(token, key, {algorithms: ["HS256"]}). Tokens that name any other algorithm are refused with 401. Also check exp, iss and aud, and use a library that rejects none by default.
Code from elsewhere: SRI, signed updates, pipelines
Code is data too. A page that loads <script src="https://cdn.example/lib.js"> runs whatever that URL serves today. If the CDN is compromised, the attacker's code runs with full access to the shop's page, and the shop's own log shows nothing (Demo 3: swapped CDN script). Subresource Integrity puts the expected hash in the tag:
<script src="https://cdn.example/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
The browser hashes the bytes it receives and refuses to run the file if the hash differs. The same idea protects larger pieces of code:
- Updates and firmware are signed by the vendor. The updater verifies the signature with a public key it already has before installing. An auto-updater that downloads over plain HTTP and installs whatever arrives is CWE-494.
- CI/CD pipelines are part of the software. Who can change the build scripts, the runners and the artifact store? Builds should be reproducible, artifacts signed, and a deploy should only accept artifacts its own pipeline signed.
- Dependencies are pinned by version and hash (lock files). The wider topic is A03, Software Supply Chain Failures.
Insecure deserialization
Some formats can describe objects of any class, for example Java serialization, Python pickle, PHP unserialize and some YAML loaders. When an app turns attacker-supplied bytes back into objects, those objects' constructors and magic methods run during loading. They run before any of the app's own checks, which can be enough to run code on the server (CWE-502). The page doesn't animate this. The defences are simple to state:
- Don't deserialize untrusted data with a format that can build arbitrary types. Use plain data formats (JSON) and validate them against a schema.
- If an object has to travel through the client, sign it as in the cookie demo and verify the signature before deserializing.
- Where a native format can't be avoided, use the platform's allow-list of classes (for example Java's
ObjectInputFilter) and run the code with few privileges.
How to prevent it
| Where the data or code comes from | Check |
|---|---|
| Cookie, hidden field, any state the client holds | Keep it on the server; otherwise an HMAC with a server-side key, checked before use |
| Signed token (JWT, SAML) | Pinned algorithm and key, rejects none; check expiry, issuer, audience |
| Script or stylesheet from a CDN | Subresource Integrity hash, or host it yourself |
| Update, firmware, plugin | Vendor signature verified before install |
| Build artifact | Signed by your pipeline; reviewed changes to pipeline config |
| Serialized objects | Don't accept them from clients; JSON plus schema, or sign then verify first |
Log integrity failures (a bad signature is almost never an accident) and alert on them, as the fixed app does on the canvas.
OWASP Top 10:2025 series
- A01 Broken Access Control
- A02 Security Misconfiguration
- A03 Software Supply Chain Failures
- A04 Cryptographic Failures
- A05 Injection
- A06 Insecure Design
- A07 Authentication Failures
- A08 Software or Data Integrity Failures (this page)
- A09 Security Logging and Alerting Failures
- A10 Mishandling of Exceptional Conditions
See also How HTTP Cookies Work, OAuth and Digital Signatures.
What the page leaves out
Real HMAC and hash values (the canvas shows shortened, made-up ones); key rotation and key ids (kid); RS256/ES256 tokens and algorithm confusion as an animation; mass assignment (CWE-915) and how frameworks bind request fields to objects; deserialization attacks themselves; code signing infrastructure (certificates, Sigstore, transparency logs); and how SRI interacts with CORS and with scripts that change on every release.