A08:2021 – Software and Data Integrity Failures is another category reorganized in 2021: it stems from recognizing that many breaches occur because software trusts data or code without verifying its integrity. Three related things fit here: insecure deserialization (a category of its own in 2017, now living inside A08), the lack of integrity verification in updates and CI/CD pipelines, and the use of data or dependencies without checking their provenance.

The common thread is unverified trust. If BazarNube's Java module deserializes an object that came from outside without checking anything, it can end up running the attacker's code. If the pipeline deploys an artifact without verifying its signature, it may deploy a tampered one. In this lesson we attack both fronts: deserialization in the legacy Java and a pipeline with no integrity verification.

Legal and ethical notice: the exploitation examples are illustrative and use fake data. Practice only on systems you own or have explicit authorization to test. Insecure deserialization can lead to remote code execution: treat it with extreme care and only in controlled environments.

Contents

  1. What an integrity failure is
  2. Insecure deserialization: the Java module case
  3. How a deserialization is exploited (illustrative)
  4. Preventing insecure deserialization
  5. Integrity of updates and the CI/CD pipeline
  6. Unverified dependencies and data
  7. Common mistakes, exercises and solutions

  1. What an integrity failure is

Integrity guarantees that a piece of data or an artifact has not been altered and comes from who it claims to. An integrity failure occurs when the system acts on something without checking it: deserializes an arbitrary object, installs an unsigned update, runs a downloaded script without verifying its hash. The cross-cutting defense is verify before trusting: digital signatures, hashes, authenticated channels.

  1. Insecure deserialization: the Java module case

Serializing is converting an object into bytes to store or transmit it; deserializing is the reverse. The danger appears when you deserialize attacker-controlled data with a mechanism that can instantiate classes and run logic during the process. Java's native serialization is the classic example.

BazarNube's legacy module stores the cart state in a cookie serialized with Java, "to avoid touching the database":

// CartController.java (legacy module) — VULNERABLE to insecure deserialization
public Cart loadCart(String cookieValue) throws Exception {
    byte[] data = Base64.getDecoder().decode(cookieValue);
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
    return (Cart) ois.readObject();   // deserializes whatever comes in the cookie
}

The problem: ObjectInputStream.readObject() on client data. The cookie is controlled by the user; they can replace the serialized cart with a malicious object that leverages a "gadget chain" (a chain of classes present on the classpath whose deserialization process ends in command execution).

  1. How it's exploited (illustrative)

The attacker doesn't send a Cart; they send a specially crafted serialized object built with known tools (the kind that generate gadget chains from common classpath libraries). When readObject() is called, deserialization walks that chain and, as a side effect, executes a command on the server —for example, opening a reverse connection or reading files. No password needs to be guessed: it's enough for the endpoint to deserialize untrusted data and for an exploitable chain to exist on the classpath.

flowchart LR
  A[Attacker builds a malicious serialized object] --> B[Sends it in the cart cookie]
  B --> C[readObject on the server]
  C --> D[Gadget chain from the classpath]
  D --> E[Command execution on the server]

It's one of the most severe vulnerabilities: it usually means remote code execution.

  1. Preventing insecure deserialization

The golden rule: don't deserialize untrusted data with mechanisms that can instantiate arbitrary objects. In order of preference:

  1. Don't serialize objects on the client. Store only an identifier and retrieve the state from the server. The cart should live in the database, referenced by a session id.
  2. Use data formats, not object formats. JSON with a parser that does not instantiate arbitrary types (with no polymorphic type handling enabled). Deserialize into a known structure and validate.
  3. If binary serialization is unavoidable, apply an allowlist of permitted classes (ObjectInputFilter in Java) and verify the data's integrity with a signature/HMAC.

Fix: server-side state + validated JSON

// CartController.java — SECURE: the cookie only carries an opaque id
public Cart loadCart(String sessionId) {
    // The real state lives in the database, not on the client
    return cartRepository.findBySession(sessionId)
        .orElseGet(Cart::new);
}

And if some flow had to accept JSON from the client, deserialize into a concrete type without enabling polymorphic type handling:

// Secure JSON: mapping to a known type, no default typing
ObjectMapper mapper = new ObjectMapper();
// Do NOT enable activateDefaultTyping(): that reintroduces the gadget risk
CartDto dto = mapper.readValue(json, CartDto.class);
validate(dto);

As reinforcement (defense in depth) for data that must travel to and from the client, sign its integrity:

// HMAC to detect tampering of data that travels to the client
String payload = base64(json);
String mac = hmacSha256(SECRET, payload);   // key from the secrets manager
// On receipt: recompute the HMAC and compare in constant time before using the data
Approach Security
ObjectInputStream on client data Very dangerous (RCE)
JSON with default typing enabled Dangerous (gadgets)
JSON to a concrete type + validation Secure
State on the server, only id on the client Secure (recommended)
Signed data (HMAC/signature) + validation Secure for data that must travel

  1. Integrity of updates and the CI/CD pipeline

Integrity doesn't end at deserialization. The pipeline that builds and deploys BazarNube is a high-value target: if an attacker injects code into the build process or replaces an artifact, they compromise all users at once (a supply chain attack).

BazarNube's pipeline had typical oversights:

# VULNERABLE pipeline (summary)
steps:
  - run: curl -s https://example.test/install.sh | bash   # runs script without verifying
  - run: docker pull myregistry/app:latest                 # mutable tag, no signature
  - deploy: kubectl apply -f k8s/                          # without verifying the artifact

Problems: running a downloaded script without verifying its hash, using images with a mutable tag (latest, which can change under your feet), and deploying artifacts without verifying their signature. Version with integrity controls:

# SECURE pipeline (summary)
steps:
  - run: |
      curl -s -o install.sh https://example.test/install.sh
      echo "<expected_hash>  install.sh" | sha256sum -c -   # verifies integrity
      bash install.sh
  - run: docker pull myregistry/app@sha256:<digest>          # image by immutable digest
  - run: cosign verify myregistry/app@sha256:<digest>        # verifies the artifact signature
  - deploy: kubectl apply -f k8s/

Key integrity controls in CI/CD:

  • Pin artifacts by immutable digest, not by mutable tags.
  • Sign and verify artifacts and images (artifact signing, build provenance).
  • Verify hashes of everything downloaded in the pipeline.
  • Protect the pipeline as sensitive code: least permissions, managed secrets, change review of the pipeline definition itself.

  1. Unverified dependencies and data

A08 overlaps with A06 (components) on the provenance front: installing dependencies without verifying their integrity (ignoring lockfile hashes, using untrusted registries) is an integrity failure. Make sure to:

  • Respect the integrity hashes of lockfiles (package-lock.json, pom.xml with checksums).
  • Install from controlled registries (proxy/internal registry), not arbitrary sources.
  • Verify the signature of critical artifacts when available.

Common Mistakes and Tips

  • Deserializing client data with ObjectInputStream. Avoid it; keep state on the server.
  • Enabling JSON default typing. It reintroduces the gadget risk; deserialize into concrete types.
  • Trusting mutable tags (latest). Use immutable digests and signatures.
  • Downloading and running scripts without verifying the hash. Always verify the integrity of what you download.
  • Treating the pipeline as non-critical. It's a supply chain target; protect it.
  • Ignoring lockfile hashes. They're your dependency integrity verification.
  • Tip: apply "verify before trusting" to any data or artifact crossing a boundary: objects, updates, images, dependencies.

Exercises

Exercise 1. Why is storing the cart as a serialized Java object in a cookie dangerous, and how would you redesign it to eliminate the class of vulnerability?

Exercise 2. A pipeline does docker pull myregistry/app:latest and deploys. List two integrity problems and their fixes.

Exercise 3. A colleague proposes "solving" the deserialization by encrypting the cookie with AES. Does it eliminate the insecure deserialization risk? Qualify your answer.

Solutions

Solution 1. It's dangerous because readObject() on user-controlled data can instantiate a gadget chain from the classpath and lead to command execution (RCE). Redesign: don't serialize objects on the client; store the cart state in the database and send the client only an opaque session identifier. That way the server never deserializes untrusted data.

Solution 2. (1) Mutable latest tag: the content can change without your knowing → use an immutable @sha256:<digest>. (2) No signature verification: the image could have been replaced → sign it at build time and verify (cosign verify) before deploying. Also, restrict the source registry.

Solution 3. It helps but doesn't eliminate the class on its own. Encrypting/signing with a well-protected secret key prevents an external attacker from forging the cookie, and it's a valid defense (integrity/confidentiality). But if the key leaks, or the flow accepts serialized data through other paths, the RCE risk from deserialization persists. The underlying solution is still not to deserialize untrusted objects: signing is defense in depth, not a substitute for the redesign.

Conclusion

A08 teaches us to not trust without verifying. In BazarNube we eliminated the cart's insecure deserialization by moving the state to the server and using JSON mapped to concrete types, and we hardened the pipeline with artifacts by digest, verified signatures, and hashes of everything downloaded. The principle "verify before trusting" covers objects, updates, images and dependencies.

Backlog entry — A08: cart redesigned (state in DB, only id on client); ObjectInputStream on external data and JSON default typing forbidden; pipeline with images by digest, cosign verify and hash verification; lockfile hashes respected.

We've prevented a lot. But no prevention is perfect: when something happens, we need to see it. The next lesson tackles A09:2021 – Security Logging and Monitoring Failures, with the logging of BazarNube's API and its link to the incident case in module 8.

OWASP Course: Guidelines and Standards for Web Application Security

Module 1: Introduction to OWASP

Module 2: Main OWASP Projects

Module 3: OWASP Top Ten 2021 in Depth

Module 4: OWASP ASVS (Application Security Verification Standard)

Module 5: OWASP SAMM (Software Assurance Maturity Model)

Module 6: OWASP ZAP (Zed Attack Proxy)

Module 7: Best Practices and Recommendations

Module 8: Practical Exercises and Case Studies

Module 9: Assessment and Certification

© Copyright 2026. All rights reserved