Returning a Mutable Object to an Untrusted Caller

Description

Returning a Mutable Object to an Untrusted Caller is a vulnerability that occurs when functions return references to mutable internal data without cloning. When non-cloned mutable data is returned, external calling code can modify it, potentially violating the class's internal state assumptions and breaking encapsulation. This weakness is the counterpart to CWE-374 (Passing Mutable Objects to an Untrusted Method) - together they form the two directions of mutable object exposure.

Risk

Returning mutable objects to untrusted callers allows those callers to tamper with internal data that they shouldn't have access to modify. This violates access control and data integrity principles. Security-relevant data like permission sets, authentication states, or configuration may be altered by code that receives the mutable reference. Internal invariants that the class depends on can be broken, causing subsequent operations to behave incorrectly or fail. The modification may not be detected until much later, making debugging difficult.

Solution

Clone all mutable data before returning references to external code. Declare returned data as constant or immutable where appropriate. Use Collections.unmodifiableList() or similar methods in Java. Return defensive copies that the caller can modify freely without affecting internal state. Consider using immutable data structures from the start. For arrays, return a new array copy rather than the original. Document clearly when returned objects are mutable copies versus views.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Data can be tampered with by functions that shouldn't have access to modify it, violating access control and integrity principles.

Example Code

Vulnerable Code

// Vulnerable: Returning mutable internal object
public class VulnerableClinicalTrial {
    private List<Patient> patients = new ArrayList<>();
    private Date startDate;
    private Map<String, String> config = new HashMap<>();

    public List<Patient> getPatients() {
        // Vulnerable: Returns direct reference to internal list
        return patients;
        // Caller can add/remove patients!
    }

    public Date getStartDate() {
        // Vulnerable: Date is mutable
        return startDate;
        // Caller can modify: getStartDate().setTime(0);
    }

    public Map<String, String> getConfig() {
        // Vulnerable: Returns internal map
        return config;
    }
}

// Attacker code
VulnerableClinicalTrial trial = getTrial();
trial.getPatients().clear();  // Deletes all patients!
trial.getStartDate().setTime(0);  // Corrupts date
trial.getConfig().put("encryption", "none");  // Disables security
# Vulnerable: Returning mutable internal state
class VulnerableUserManager:
    def __init__(self):
        self._users = []
        self._permissions = {}

    def get_users(self):
        # Vulnerable: Returns internal list
        return self._users

    def get_permissions(self):
        # Vulnerable: Returns internal dict
        return self._permissions

# Attacker code
manager = VulnerableUserManager()
manager.get_users().append(malicious_admin)  # Add unauthorized user
manager.get_permissions()["admin"] = True  # Escalate privileges
// Vulnerable: Returning pointer to internal buffer
typedef struct {
    char password[64];
    int privilege_level;
} UserSession;

static UserSession current_session;

// Vulnerable: Returns pointer to internal data
UserSession* get_current_session() {
    return &current_session;
}

// Attacker code
UserSession *session = get_current_session();
session->privilege_level = ADMIN_LEVEL;  // Privilege escalation

Fixed Code

// Fixed: Clone mutable objects before returning
public class SecureClinicalTrial {
    private List<Patient> patients = new ArrayList<>();
    private Date startDate;
    private Map<String, String> config = new HashMap<>();

    public List<Patient> getPatients() {
        // Fixed: Return defensive copy
        List<Patient> copy = new ArrayList<>();
        for (Patient p : patients) {
            copy.add(p.clone());  // Deep copy if Patient is mutable
        }
        return copy;

        // Or return unmodifiable view:
        // return Collections.unmodifiableList(patients);
    }

    public Date getStartDate() {
        // Fixed: Return clone of mutable Date
        return (Date) startDate.clone();
    }

    public Map<String, String> getConfig() {
        // Fixed: Return unmodifiable view
        return Collections.unmodifiableMap(config);
    }
}

// Better: Use immutable types
public class ImmutableClinicalTrial {
    private final List<Patient> patients;
    private final Instant startDate;  // Instant is immutable
    private final Map<String, String> config;

    public ImmutableClinicalTrial(List<Patient> patients,
                                   Instant startDate,
                                   Map<String, String> config) {
        this.patients = List.copyOf(patients);  // Immutable copy
        this.startDate = startDate;
        this.config = Map.copyOf(config);  // Immutable copy
    }

    public List<Patient> getPatients() {
        return patients;  // Already immutable
    }

    public Instant getStartDate() {
        return startDate;  // Immutable
    }
}
# Fixed: Return copies of mutable objects
import copy

class SecureUserManager:
    def __init__(self):
        self._users = []
        self._permissions = {}

    def get_users(self):
        # Fixed: Return deep copy
        return copy.deepcopy(self._users)

    def get_permissions(self):
        # Fixed: Return copy
        return self._permissions.copy()

    # Fixed: Return immutable view using tuple/frozenset
    def get_user_ids(self):
        return tuple(user.id for user in self._users)

# Using dataclasses with frozen=True for immutability
from dataclasses import dataclass
from typing import Tuple

@dataclass(frozen=True)
class ImmutableUserData:
    users: Tuple[str, ...]  # Immutable tuple
    permissions: frozenset  # Immutable set
// Fixed: Return copy of data
typedef struct {
    char password[64];
    int privilege_level;
} UserSession;

static UserSession current_session;

// Fixed: Return copy, not pointer
int get_current_session(UserSession *copy) {
    if (copy == NULL) {
        return -1;
    }
    // Fixed: Copy data to caller-provided buffer
    memcpy(copy, &current_session, sizeof(UserSession));
    return 0;
}

// Fixed: Return const pointer for read-only access
const UserSession* get_session_readonly() {
    return &current_session;
    // Compiler enforces read-only
}

CVE Examples

No specific CVEs are listed for this CWE. The vulnerability pattern appears in:

  • Java applications exposing internal collections
  • APIs returning Date objects or arrays
  • Object-oriented code with inadequate encapsulation

References

  1. MITRE Corporation. "CWE-375: Returning a Mutable Object to an Untrusted Caller." https://cwe.mitre.org/data/definitions/375.html
  2. Joshua Bloch. "Effective Java." Item 50: Make defensive copies when needed.