Skip to content

CWE-498: Cloneable Class Containing Sensitive Information

Overview

Some object-oriented languages provide a duplication mechanism that can bypass a class's normal construction path, so any code holding a reference to an object carrying credentials, cryptographic keys, tokens, or a security context can produce an untracked copy of it, skipping the validation or authorization the constructor would enforce. MITRE lists the applicable languages as Java, C#, C++ and object-oriented code generally, and the three differ in how much the language does for you:

  • Java is the case the CWE is named for. A class that implements Cloneable and calls super.clone() gets a field-by-field copy from Object.clone() without writing any copying code. Object.clone() is itself protected, so the bypass is reachable from outside only once the class publishes a public clone() - which is exactly what the pattern asks authors to do.
  • C++ has no interface to implement. The copy constructor and copy assignment operator are declared implicitly unless you suppress them, so a class holding a secret is copyable by default and stays copyable until it says otherwise.
  • C# does neither automatically for an ordinary class. ICloneable declares a Clone() you write yourself, and MemberwiseClone() is protected, so the duplication path exists only where somebody added it. A record is the exception and is easy to miss: the compiler synthesizes a copy constructor and a clone method for it, so original with { ... } produces a copy carrying every field it did not overwrite, with no ICloneable anywhere in the source (measured on .NET 10). A record holding a secret is copyable the moment it is declared.

Relationship to Other CWEs

CWE-498 is a Variant, ChildOf CWE-668 (Exposure of Resource to Wrong Sphere): the sensitive object is reachable from a sphere - any caller holding a reference - that should not have been able to duplicate it. MITRE records it as CanPrecede CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor), which is what the untracked copy usually becomes.

CWE-491 (Public cloneable() Method Without Final, "Object Hijack") has no page here. It and CWE-498 are reported on the same class and ask different questions. CWE-498 asks whether the data should be copyable at all: the fix is to remove the cloning path from a class holding secrets. CWE-491 asks whether a subclass can exploit a non-final clone() to obtain an instance that never ran the constructor - an object in a state the class believes is unreachable, whether or not it holds secrets. A class carrying credentials with a public, non-final clone() earns both. Removing the cloning path does not on its own close them: on a non-final class a subclass can implement Cloneable itself, and super.clone() reaches the private superclass fields it could not otherwise touch. Sealing that route is the other half of the fix, and either spelling works - make the class final, or give it a final clone() that throws CloneNotSupportedException, which a subclass cannot override and cannot get past with super.clone() (measured on JDK 26). MITRE's own CWE-491 mitigation names the final method rather than a final class, so a class that must stay extensible has a way through.

OWASP Classification

A01:2025 - Broken Access Control

Risk

Medium: A caller holding a reference can copy a sensitive object without passing the access checks the constructor applies. The copies are untracked, so clearing the secret means finding and scrubbing all of them, and a cloned security context can outlive the authorization it was created under.

Remediation Steps

Core Principle: A class holding sensitive data should have exactly one way to be constructed - its constructor - and no mechanism that produces a copy without going through it.

Trace the Data Path

  • Source: Any class that stores credentials, keys, tokens, PII, or a security/authorization context
  • Sink: The language's cloning mechanism (clone(), a copy constructor, a deserialization-based copy) being reachable on that class
  • Missing control: No restriction preventing the cloning mechanism from being invoked, and no authorization check if cloning is intentionally supported

Disable Cloning for Sensitive Classes (Primary Defense)

// SECURE - pseudo-code
class Credentials:
    // no cloning interface implemented
    // class marked non-subclassable, so a subclass can't add cloning support

    constructor(username, password):
        this.username = username
        this.password = copy_of(password)   // defensive copy on construction, not on clone

Take the duplication path away from a class carrying sensitive data - but note that "do not implement it" is only the right instruction where the language gives you nothing by default. In Java and C# that holds: leave Cloneable unimplemented and no public clone() exists, and write no Clone() and ICloneable is not in play. In C++ the opposite is true, because the copy constructor and copy assignment operator are declared for you; not implementing them is precisely what leaves the class copyable. There you have to suppress them explicitly with = delete.

Provide a Factory Method Instead of Cloning, If a Copy Is Needed

Where the application genuinely needs a "copy" operation (e.g. renewing a token with the same permissions), expose it as an explicit factory method or equivalent constructor call - not the language's generic cloning hook - so the copy still goes through the class's own validation logic.

If Cloning Cannot Be Avoided, Gate and Audit It

  • Check authorization before producing the copy, not after
  • Deep-copy every mutable field - never rely on the language's default shallow-copy behavior for arrays, collections, or nested objects holding sensitive data
  • Log the operation so a cloned credential is tracked like any other credential issuance

Test the Fix

  • Attempt to clone/copy each sensitive class outside of its intended construction path and verify it's rejected
  • Where cloning is intentionally supported, verify an unauthorized caller's attempt is rejected and that the resulting copy's mutable fields are independent of the original's
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
class Credentials implements Cloneable:
    username
    password   // sensitive

    function clone():
        return shallow_copy(this)   // bypasses the constructor entirely, shares mutable fields

// Attack: any code holding a Credentials reference calls clone()
// Result: an untracked copy of the password exists, with no authorization check

Why this is vulnerable: the duplication mechanism is a second constructor that the class did not write and cannot see. Everything the real constructor does - authorising the caller, validating the inputs, recording the object in an audit trail or a live-session count - is skipped, and what comes out is indistinguishable from an object that went through all of it. Construction was meant to be the only way in, and an inherited duplication path makes that untrue.

The copy is also shallower than it looks. A default duplication copies field values, so a reference field - the array holding the key, the buffer holding the token - ends up shared rather than duplicated. Clearing the secret in one object clears it in the other, so cleanup on one code path empties an object another path is still using, and a change made through either reference is visible through both.

Secure Patterns

// SECURE - pseudo-code
class Credentials:  // no cloning interface implemented, class is non-subclassable
    username
    password

    constructor(username, password):
        this.username = username
        this.password = copy_of(password)

    static function create(username, password):
        return new Credentials(username, password)   // the only way to produce an instance

Why this works: With no cloning mechanism implemented and no subclass able to add one, the constructor is the only path that produces an instance. Every instance has gone through whatever validation the constructor enforces, and obtaining a second copy of an existing instance's data means calling create() again with data the caller already had legitimate access to.

Language-Specific Guidance

  • Java - Cloneable/Object.clone(), final classes, defensive copying, gated cloning with authorization and audit logging

Additional Resources