CWE-498: Cloneable Class Containing Sensitive Information
Overview
Cloneable classes with sensitive data (passwords, keys, tokens) allow unauthorized duplication through clone() method, creating uncontrolled copies that bypass security controls, aren't tracked, and may not be properly cleared from memory.
Risk
Medium: Cloneable sensitive classes enable unauthorized data copies, bypass access controls, create untracked credential copies, prevent secure memory clearing, and allow privilege escalation through cloned security contexts.
Remediation Steps
Core principle: Do not allow cloning/copying to leak secrets; prevent cloning or scrub sensitive fields on clone/serialize.
Locate Cloneable Classes with Sensitive Data
When reviewing security scan results:
- Examine data_paths: Identify classes that implement Cloneable and contain sensitive data
- Find sensitive fields: Passwords, keys, tokens, PII, credentials, security contexts
- Check clone() implementations: Look for shallow copies of sensitive data
- Review class hierarchy: Check if sensitive classes can be subclassed
- Assess risk: Can cloning bypass access controls or create untracked copies
Sensitive data types:
- Authentication credentials (passwords, tokens)
- Cryptographic keys (encryption keys, signing keys)
- Security contexts (user roles, permissions)
- Personal information (SSN, credit cards)
- Session data
Make Class Final to Prevent Cloning (Primary Defense)
// Prevent cloning by making class final and not implementing Cloneable
public final class Credentials { // final prevents subclassing
private final String username;
private final char[] password;
private final byte[] encryptionKey;
public Credentials(String username, char[] password, byte[] key) {
this.username = username;
this.password = Arrays.copyOf(password, password.length);
this.encryptionKey = Arrays.copyOf(key, key.length);
}
// No clone method - cloning not supported
// Class is final - can't be subclassed to add clone()
public void clear() {
Arrays.fill(password, '\0');
Arrays.fill(encryptionKey, (byte) 0);
}
}
Why this works: Making a class final prevents it from being subclassed, which prevents attackers from creating a subclass that implements cloning. Not implementing Cloneable means clone() will throw CloneNotSupportedException.
Override clone() to Prevent Cloning
If class can't be final (e.g., part of framework):
public class SecureToken {
private String token;
private byte[] secret;
private long expiryTime;
@Override
public final Object clone() throws CloneNotSupportedException {
// Explicitly prevent cloning of sensitive data
throw new CloneNotSupportedException(
"Cloning of SecureToken is not permitted for security reasons"
);
}
// Alternative: provide controlled copy method
public SecureToken createRenewal() {
// Check permissions
if (!hasPermission("token.renew")) {
throw new SecurityException("Not authorized to renew token");
}
// Create new token (not a clone)
return new SecureToken(generateNewToken());
}
}
Implementation notes:
- Make clone()
finalso subclasses can't override it - Throw CloneNotSupportedException with clear message
- Provide alternative controlled copy methods if needed
Don't Implement Cloneable Interface
// WRONG - implements Cloneable with sensitive data
public class Password implements Cloneable { // Don't do this!
private char[] passwordChars;
@Override
public Object clone() {
try {
Password cloned = (Password) super.clone();
// Even with deep copy, creates untracked copy
cloned.passwordChars = passwordChars.clone();
return cloned;
} catch (CloneNotSupportedException e) {
return null;
}
}
}
// CORRECT - no Cloneable interface
public final class Password { // No implements Cloneable
private final char[] passwordChars;
public Password(char[] password) {
this.passwordChars = Arrays.copyOf(password, password.length);
}
// Provide controlled access
public char[] getPassword() {
return Arrays.copyOf(passwordChars, passwordChars.length);
}
public void clear() {
Arrays.fill(passwordChars, '\0');
}
// No clone method at all
}
Implement Deep Clone with Access Control (If Cloning Required)
If cloning is genuinely needed:
public class ApiKey implements Cloneable {
private String keyId;
private byte[] secretKey;
private Set<String> permissions;
@Override
protected Object clone() throws CloneNotSupportedException {
// Check security permissions before allowing clone
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("cloneApiKey"));
}
// Additional business logic check
if (!currentUser().hasRole("ADMIN")) {
throw new CloneNotSupportedException("Only admins can clone API keys");
}
// Perform deep copy
ApiKey cloned = (ApiKey) super.clone();
cloned.secretKey = this.secretKey.clone();
cloned.permissions = new HashSet<>(this.permissions);
// Audit logging
auditLog.log("ApiKey cloned by " + currentUser().getName(),
"keyId=" + keyId,
"timestamp=" + Instant.now());
// Track cloned instances
registerClone(cloned);
return cloned;
}
}
Access control measures:
- Check SecurityManager permissions
- Verify user authorization
- Perform deep copy (not shallow)
- Log all cloning operations
- Track cloned instances
Verify Cloning is Prevented
Manual verification steps:
Check class does not implement Cloneable
Review sensitive classes to ensure they don't implement Cloneable
# Search for classes implementing Cloneable
grep -r "implements.*Cloneable" src/
# Check specific sensitive classes
grep "class Credentials\|class SecurityContext\|class User" src/ -A 1 | grep Cloneable
Verify clone() method throws exception
If clone() is inherited, ensure it throws CloneNotSupportedException
// Check the implementation:
@Override
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cloning not allowed");
}
Confirm class is final
Prevent subclasses from overriding clone protection
# Verify sensitive classes are final
grep -E "class (Credentials|User|SecurityContext|Session)" src/ | grep final
Manual testing
Attempt to clone sensitive objects and verify it fails
// In your development environment, try:
Credentials creds = new Credentials("user", "pass123".toCharArray());
try {
Credentials cloned = (Credentials) creds.clone();
System.err.println("ERROR: Cloning succeeded when it should fail!");
} catch (CloneNotSupportedException e) {
System.out.println("✓ Cloning correctly prevented: " + e.getMessage());
}
Automated verification
# Use static analysis to detect Cloneable implementation
# PMD rule: AvoidCloneableImplementation (for security-sensitive classes)
# SpotBugs detector: CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE
mvn pmd:check
mvn spotbugs:check
Code review checklist
- Sensitive classes do NOT implement
Cloneable - If
clone()exists, it throwsCloneNotSupportedException - Classes are marked
finalto prevent subclass bypass - Copy constructors are used instead of cloning
- Defensive copying is used for mutable fields
Monitoring
- Log any clone() calls on sensitive classes
- Alert on CloneNotSupportedException from sensitive classes
- Track object lifecycle for sensitive data
Common Vulnerable Patterns
Cloneable Class with Shallow Copy of Sensitive Data
// VULNERABLE - Cloneable with sensitive data
public class User implements Cloneable {
private String username;
private String password; // Sensitive!
private byte[] secretKey; // Sensitive!
@Override
public Object clone() {
try {
return super.clone(); // Shallow copy!
} catch (CloneNotSupportedException e) {
return null;
}
}
}
// Attack example:
User user = authenticate();
User clonedUser = (User) user.clone(); // Unauthorized copy!
// Now clonedUser has password and secretKey
// Result: Attacker has cloned credentials bypassing access controls
Attack Scenarios
Bypass Access Control via Cloned Security Context
// Attack example: Clone privileged context for later use
SecurityContext context = getSecurityContext();
if (context.isAdmin()) {
// Clone and use later when not admin
SecurityContext cloned = (SecurityContext) context.clone();
// Use cloned context to bypass checks
}
Untracked Credentials After Clearing
// Attack example: Keep copy of credentials after original is cleared
Credentials creds = login();
Credentials backup = (Credentials) creds.clone();
// Original cleared but backup still has password
creds.clear();
// backup still contains the password in memory
Secure Patterns
Use Final Class with No Cloning Support
// SECURE - Immutable final class with no cloning
public final class SecureCredentials {
private final String username;
private final char[] password;
public SecureCredentials(String user, char[] pass) {
this.username = user;
this.password = Arrays.copyOf(pass, pass.length);
}
// Factory method instead of clone
public static SecureCredentials create(String user, char[] pass) {
return new SecureCredentials(user, pass);
}
public void clear() {
Arrays.fill(password, '\0');
}
}
Why this works: Declaring the class as final prevents subclassing, which means attackers cannot create a derived class that implements Cloneable or overrides clone() to create unauthorized copies. By not implementing the Cloneable interface, any attempt to call clone() will throw CloneNotSupportedException. The defensive copy in the constructor (Arrays.copyOf()) ensures that the original password array passed in cannot be modified externally. Instead of cloning, the create() factory method provides a controlled way to create new instances while maintaining proper security controls. The clear() method allows secure disposal of sensitive data by overwriting the password array with zeros, ensuring only one tracked instance needs to be cleared rather than worrying about untracked clones.
Security Checklist
- Sensitive classes are
final - Sensitive classes don't implement
Cloneable - If clone() exists, it throws CloneNotSupportedException
- clone() method is
final(can't be overridden) - No shallow copies of sensitive data
- Cloning is logged/audited if allowed
- Access controls checked before cloning