Access to Critical Private Variable via Public Method

Description

Access to Critical Private Variable via Public Method is an encapsulation vulnerability where software defines a public method that reads or modifies a private variable without proper validation or access control. While making variables private is correct, providing unrestricted public accessors (getters/setters) effectively negates the protection. Attackers can use these public methods to modify critical variables to unexpected values, violating security assumptions elsewhere in the code. Similarly, exposing private data through public getters can leak sensitive information.

Risk

This weakness allows attackers to manipulate critical internal state through the public interface. When public methods modify private security-relevant variables without validation, attackers can inject malicious values that bypass security checks, alter control flow, or escalate privileges. For example, a public setter for a user role variable without validation could allow privilege escalation. Exposing private data through getters may reveal sensitive information like internal IDs, session data, or partial credentials. The vulnerability is particularly dangerous because the code appears to follow encapsulation principles (private variables), masking the actual exposure.

Solution

Implement proper validation in all public methods that access private variables. Setters should validate input ranges, types, and business rules before modification. Consider making setters more restrictive or eliminating them entirely for security-critical fields. Getters should return defensive copies for mutable objects and consider whether the data truly needs to be exposed. Apply the principle of least privilege—only expose necessary operations. Use access control checks within methods to verify the caller has permission to perform the operation. For critical security state, consider making fields final after initialization.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Modify Application Data - Public methods allow unauthorized modification of critical private variables when validation is missing.
ConfidentialityScope: Confidentiality

Read Application Data - Public getter methods may expose sensitive private information without proper access control.
Access ControlScope: Access Control

Bypass Protection Mechanism - Attackers can modify security-relevant private variables to bypass access controls or elevate privileges.

Example Code

Vulnerable Code

// Vulnerable: Public setter modifies critical private variable without validation
class Product {
private:
    float price;
    int quantity;
    bool isDiscounted;

public:
    // Vulnerable: No validation on price change
    void setPrice(float newPrice) {
        price = newPrice;  // Can be set to negative or zero
    }

    // Vulnerable: Exposes ability to manipulate discount flag
    void setDiscounted(bool discounted) {
        isDiscounted = discounted;  // Anyone can apply discounts
    }

    float getPrice() const { return price; }
};

// Exploitation
void exploit(Product& p) {
    p.setPrice(-100.0);    // Negative price - refund on purchase?
    p.setDiscounted(true); // Free discounts
}
// Vulnerable: Typo causes wrong variable to be modified
public class UserProfile {
    private int profileId;   // PID - normal user identifier
    private int userId;      // UID - system user identifier (more privileged)

    // Vulnerable: Typo allows modification of wrong field
    public void setProfileId(int pid) {
        this.userId = pid;  // BUG: Should be this.profileId = pid
        // This allows user to modify their system userId!
    }

    public int getProfileId() {
        return profileId;
    }

    public int getUserId() {
        return userId;
    }
}

// Even without bugs, unvalidated setters are problematic:
public class Session {
    private String role;
    private boolean authenticated;
    private long sessionId;

    // Vulnerable: Allows arbitrary role assignment
    public void setRole(String role) {
        this.role = role;  // Can set to "admin", "superuser", etc.
    }

    // Vulnerable: Allows bypassing authentication
    public void setAuthenticated(boolean auth) {
        this.authenticated = auth;  // Can set true without actual auth
    }

    // Vulnerable: Exposes internal session ID
    public long getSessionId() {
        return sessionId;
    }
}
# Vulnerable: Python property provides unrestricted access
class BankAccount:
    def __init__(self, account_number):
        self._account_number = account_number
        self._balance = 0
        self._daily_limit = 1000
        self._is_frozen = False

    # Vulnerable: No validation on balance setter
    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        self._balance = value  # Can set to any value, including negative

    # Vulnerable: Allows bypassing frozen account check
    @property
    def is_frozen(self):
        return self._is_frozen

    @is_frozen.setter
    def is_frozen(self, value):
        self._is_frozen = value  # Can unfreeze own account

# Exploitation
account = BankAccount("12345")
account.balance = 1000000  # Set arbitrary balance
account.is_frozen = False  # Unfreeze compromised account

Fixed Code

// Fixed: Proper validation and controlled access
class Product {
private:
    float price;
    int quantity;
    bool isDiscounted;
    const float MINIMUM_PRICE = 0.01f;
    const float MAXIMUM_DISCOUNT = 0.50f;

public:
    Product(float initialPrice, int initialQty)
        : price(initialPrice), quantity(initialQty), isDiscounted(false) {
        if (price < MINIMUM_PRICE) {
            throw std::invalid_argument("Price must be positive");
        }
    }

    // Fixed: Validated setter with business rules
    bool setPrice(float newPrice) {
        if (newPrice < MINIMUM_PRICE) {
            return false;  // Reject invalid price
        }
        price = newPrice;
        return true;
    }

    // Fixed: No public setter for discount - use controlled method
    bool applyDiscount(float discountPercent, const std::string& authCode) {
        if (!validateAuthCode(authCode)) {
            return false;  // Require authorization
        }
        if (discountPercent > MAXIMUM_DISCOUNT) {
            return false;  // Limit discount amount
        }
        isDiscounted = true;
        price = price * (1.0f - discountPercent);
        return true;
    }

    float getPrice() const { return price; }
    bool hasDiscount() const { return isDiscounted; }

private:
    bool validateAuthCode(const std::string& code) {
        // Validate authorization code
        return code.length() > 0;  // Simplified
    }
};
// Fixed: Proper encapsulation with validation and access control
public class Session {
    private String role;
    private boolean authenticated;
    private final long sessionId;
    private static final Set<String> VALID_ROLES =
        Set.of("guest", "user", "moderator");  // Admin not settable here

    public Session(long sessionId) {
        this.sessionId = sessionId;
        this.role = "guest";
        this.authenticated = false;
    }

    // Fixed: No setter for role - use controlled promotion method
    public boolean promoteToRole(String newRole, SecurityContext context) {
        // Verify caller has permission to promote
        if (!context.canPromote(this.role, newRole)) {
            throw new SecurityException("Unauthorized role change");
        }
        // Validate role is allowed
        if (!VALID_ROLES.contains(newRole)) {
            return false;
        }
        this.role = newRole;
        return true;
    }

    // Fixed: Authentication only through proper verification
    public boolean authenticate(String password, PasswordVerifier verifier) {
        if (verifier.verify(this.sessionId, password)) {
            this.authenticated = true;
            return true;
        }
        return false;
    }

    // Fixed: No setter for sessionId - it's final

    public String getRole() {
        return role;
    }

    public boolean isAuthenticated() {
        return authenticated;
    }

    // Fixed: Session ID not exposed - use controlled comparison
    public boolean matchesSession(long otherSessionId) {
        return this.sessionId == otherSessionId;
    }
}
# Fixed: Proper validation and controlled access in Python
class BankAccount:
    def __init__(self, account_number, initial_balance=0):
        self._account_number = account_number
        self._balance = initial_balance
        self._daily_limit = 1000
        self._is_frozen = False

    @property
    def balance(self):
        return self._balance

    # Fixed: No public setter - use controlled methods instead
    def deposit(self, amount, source_verified=False):
        if amount <= 0:
            raise ValueError("Deposit amount must be positive")
        if self._is_frozen:
            raise AccountFrozenError("Cannot deposit to frozen account")
        self._balance += amount
        return self._balance

    def withdraw(self, amount, authorization_token):
        if not self._verify_authorization(authorization_token):
            raise SecurityError("Invalid authorization")
        if amount <= 0:
            raise ValueError("Withdrawal amount must be positive")
        if self._is_frozen:
            raise AccountFrozenError("Cannot withdraw from frozen account")
        if amount > self._daily_limit:
            raise LimitExceededError("Exceeds daily limit")
        if amount > self._balance:
            raise InsufficientFundsError("Insufficient balance")
        self._balance -= amount
        return self._balance

    @property
    def is_frozen(self):
        return self._is_frozen

    # Fixed: Freezing requires bank officer authorization
    def freeze_account(self, officer_id, reason):
        if not self._verify_officer(officer_id):
            raise SecurityError("Unauthorized freeze attempt")
        self._is_frozen = True
        self._log_freeze(officer_id, reason)

    def _verify_authorization(self, token):
        # Verify withdrawal authorization
        return token is not None and len(token) > 0

    def _verify_officer(self, officer_id):
        # Verify bank officer credentials
        return officer_id is not None

CVE Examples

  • CVE-2012-3400: Linux kernel XFS allowed local users to cause denial of service via public interface exposing critical internal structure manipulation.

References

  1. MITRE Corporation. "CWE-767: Access to Critical Private Variable via Public Method." https://cwe.mitre.org/data/definitions/767.html
  2. CERT C++ Coding Standard. "OOP03-CPP. Use accessor and mutator methods for data encapsulation."
  3. Oracle Java Secure Coding Guidelines. "SECCODE-6: Minimize the scope of variables."