Method Containing Access of a Member Element from Another Class
Description
Method Containing Access of a Member Element from Another Class occurs when a method for a class performs an operation that directly accesses a member element from another class. This violates the principle of encapsulation in object-oriented programming, where classes should hide their internal state and provide controlled access through methods. When one class directly accesses another class's fields, it creates tight coupling, breaks information hiding, and makes the code harder to maintain and secure.
Risk
Direct access to another class's members has indirect security implications. Bypassing accessor methods means bypassing any validation, authorization, or audit logging in those methods. Tight coupling makes security refactoring difficult. Changes to internal representation in one class break dependent classes. The exposed internal state can be manipulated in unintended ways. Security controls that should be centralized in accessor methods are scattered or absent. Code review for security becomes harder with unclear boundaries between classes.
Solution
Use accessor methods (getters/setters) instead of direct field access. Apply proper access modifiers (private, protected) to member variables. Follow the Law of Demeter - don't reach through objects. Use interfaces to define contracts between classes. Apply the Tell, Don't Ask principle - tell objects what to do rather than querying their state. Consider using immutable objects where appropriate. Use static analysis tools to detect encapsulation violations. Define clear class responsibilities to minimize inter-class dependencies.
Common Consequences
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Maintainability - Tight coupling makes code harder to modify safely. |
| Other | Scope: Other Increase Analytical Complexity - Security analysis is harder with unclear boundaries. |
| Integrity | Scope: Integrity Bypass Protection Mechanism - Validation and access control in accessors is bypassed. |
Example Code
Vulnerable Code
// Vulnerable: Direct access to another class's members
public class VulnerableBankAccount {
// Vulnerable: Public field - no encapsulation!
public double balance;
public String accountNumber;
public String ownerName;
public boolean isActive;
}
public class VulnerableTransactionProcessor {
public void processWithdrawal(VulnerableBankAccount account, double amount) {
// Vulnerable: Direct field access from another class
if (account.balance >= amount) {
account.balance -= amount; // Direct modification!
}
// No validation, no audit logging, no authorization check
}
public void transferFunds(VulnerableBankAccount from,
VulnerableBankAccount to,
double amount) {
// Vulnerable: Direct access to fields
if (from.isActive && to.isActive) { // Direct field access
if (from.balance >= amount) { // Direct field access
from.balance -= amount; // Direct modification!
to.balance += amount; // Direct modification!
}
}
// Bypasses any validation that should exist in BankAccount class
}
public void closeAccount(VulnerableBankAccount account) {
// Vulnerable: Direct state modification
account.isActive = false; // No authorization check!
account.balance = 0; // Potential data loss!
account.ownerName = "CLOSED"; // Direct modification!
}
}
// Attack scenario:
// VulnerableBankAccount account = new VulnerableBankAccount();
// account.balance = 1000000; // Anyone can set any balance!
// account.isActive = true; // Bypass account activation process!
# Vulnerable: Direct attribute access in Python
class VulnerableUser:
def __init__(self, username, email):
# Vulnerable: All attributes publicly accessible
self.username = username
self.email = email
self.password_hash = None
self.role = 'user'
self.is_admin = False
self.login_attempts = 0
self.locked = False
class VulnerableAuthService:
def authenticate(self, user, password):
# Vulnerable: Direct access to User's internal state
if user.locked: # Direct field access
return False
if verify_password(password, user.password_hash): # Direct access
user.login_attempts = 0 # Direct modification
return True
else:
user.login_attempts += 1 # Direct modification
if user.login_attempts >= 3: # Direct access
user.locked = True # Direct modification
return False
def promote_to_admin(self, user):
# Vulnerable: Direct modification of security-critical field
user.is_admin = True # No authorization check!
user.role = 'admin' # Direct modification!
def reset_user(self, user):
# Vulnerable: Manipulates internal state directly
user.login_attempts = 0
user.locked = False
user.password_hash = None # Dangerous!
# Attack:
# user = VulnerableUser("attacker", "[email protected]")
# user.is_admin = True # Privilege escalation!
# user.role = 'admin' # Direct role assignment!
// Vulnerable: Direct field access in C#
public class VulnerableOrder
{
// Vulnerable: Public fields instead of properties
public decimal TotalAmount;
public string Status;
public List<OrderItem> Items;
public DateTime CreatedAt;
public bool IsPaid;
}
public class VulnerableOrderProcessor
{
public void ApplyDiscount(VulnerableOrder order, decimal discountPercent)
{
// Vulnerable: Direct field access and modification
decimal discount = order.TotalAmount * (discountPercent / 100);
order.TotalAmount -= discount; // No validation!
// Could set TotalAmount negative with large discount
}
public void ProcessPayment(VulnerableOrder order)
{
// Vulnerable: Direct state manipulation
order.IsPaid = true; // No payment verification!
order.Status = "PAID";
}
public void AddItem(VulnerableOrder order, OrderItem item)
{
// Vulnerable: Direct collection access
order.Items.Add(item); // Bypasses any order item validation
// Directly modify total - no recalculation
order.TotalAmount += item.Price;
}
public void ClearOrder(VulnerableOrder order)
{
// Vulnerable: Direct manipulation of all fields
order.Items.Clear();
order.TotalAmount = 0;
order.Status = "CLEARED";
order.IsPaid = false;
}
}
Fixed Code
// Fixed: Proper encapsulation with controlled access
public class FixedBankAccount {
// Private fields - not directly accessible
private double balance;
private String accountNumber;
private String ownerName;
private boolean isActive;
private final AuditLogger auditLogger;
public FixedBankAccount(String accountNumber, String ownerName,
AuditLogger auditLogger) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0;
this.isActive = false;
this.auditLogger = auditLogger;
}
// Controlled access with validation
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
public boolean isActive() {
return isActive;
}
// Business operations encapsulated in the class
public void withdraw(double amount) throws InsufficientFundsException {
validateActive();
validateAmount(amount);
if (balance < amount) {
throw new InsufficientFundsException("Insufficient funds");
}
balance -= amount;
auditLogger.log("Withdrawal", accountNumber, amount);
}
public void deposit(double amount) {
validateActive();
validateAmount(amount);
balance += amount;
auditLogger.log("Deposit", accountNumber, amount);
}
public void activate(String authorizedBy) {
if (isActive) {
throw new IllegalStateException("Account already active");
}
isActive = true;
auditLogger.log("Account activated by " + authorizedBy, accountNumber, 0);
}
public void deactivate(String authorizedBy, String reason) {
if (!isActive) {
throw new IllegalStateException("Account already inactive");
}
isActive = false;
auditLogger.log("Account deactivated: " + reason, accountNumber, balance);
}
private void validateActive() {
if (!isActive) {
throw new IllegalStateException("Account is not active");
}
}
private void validateAmount(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
}
}
// Fixed: TransactionProcessor uses methods, not field access
public class FixedTransactionProcessor {
private final AuthorizationService authService;
public FixedTransactionProcessor(AuthorizationService authService) {
this.authService = authService;
}
public void processWithdrawal(FixedBankAccount account, double amount,
User requestedBy) throws TransactionException {
// Check authorization
authService.checkPermission(requestedBy, "WITHDRAW", account);
// Delegate to account - all logic encapsulated there
account.withdraw(amount);
}
public void transferFunds(FixedBankAccount from, FixedBankAccount to,
double amount, User requestedBy) throws TransactionException {
// Authorization check
authService.checkPermission(requestedBy, "TRANSFER", from);
// Use account methods - no direct field access
from.withdraw(amount);
to.deposit(amount);
}
}
# Fixed: Proper encapsulation in Python
from dataclasses import dataclass, field
from typing import Optional
import hashlib
import secrets
class FixedUser:
"""User with proper encapsulation."""
def __init__(self, username: str, email: str):
self._username = username
self._email = email
self._password_hash: Optional[str] = None
self._role = 'user'
self._is_admin = False
self._login_attempts = 0
self._locked = False
# Read-only properties
@property
def username(self) -> str:
return self._username
@property
def email(self) -> str:
return self._email
@property
def is_locked(self) -> bool:
return self._locked
@property
def is_admin(self) -> bool:
return self._is_admin
# Controlled operations
def set_password(self, password: str) -> None:
"""Set password with proper hashing."""
if len(password) < 8:
raise ValueError("Password must be at least 8 characters")
salt = secrets.token_hex(16)
self._password_hash = self._hash_password(password, salt)
def verify_password(self, password: str) -> bool:
"""Verify password and handle login attempts."""
if self._locked:
raise AccountLockedException("Account is locked")
if self._password_hash is None:
return False
if self._verify_hash(password, self._password_hash):
self._login_attempts = 0
return True
else:
self._record_failed_attempt()
return False
def _record_failed_attempt(self) -> None:
"""Internal: Record failed login attempt."""
self._login_attempts += 1
if self._login_attempts >= 3:
self._locked = True
def unlock(self, admin_user: 'FixedUser') -> None:
"""Unlock account - requires admin."""
if not admin_user.is_admin:
raise PermissionError("Only admins can unlock accounts")
self._locked = False
self._login_attempts = 0
def promote_to_admin(self, super_admin: 'FixedUser') -> None:
"""Promote to admin - requires super admin."""
if not super_admin.is_admin:
raise PermissionError("Only admins can promote users")
self._is_admin = True
self._role = 'admin'
class FixedAuthService:
"""Authentication service using proper encapsulation."""
def __init__(self, audit_logger):
self._audit = audit_logger
def authenticate(self, user: FixedUser, password: str) -> bool:
"""Authenticate user through proper methods."""
try:
# Use user's method - don't access internal state
if user.verify_password(password):
self._audit.log(f"User {user.username} authenticated")
return True
else:
self._audit.log(f"Failed auth attempt for {user.username}")
return False
except AccountLockedException:
self._audit.log(f"Auth blocked - {user.username} is locked")
return False
// Fixed: Proper encapsulation in C#
public class FixedOrder
{
// Private fields
private decimal _totalAmount;
private string _status;
private readonly List<OrderItem> _items;
private bool _isPaid;
public FixedOrder()
{
_items = new List<OrderItem>();
_status = "PENDING";
_totalAmount = 0;
_isPaid = false;
}
// Read-only properties
public decimal TotalAmount => _totalAmount;
public string Status => _status;
public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
public bool IsPaid => _isPaid;
// Business methods with validation
public void AddItem(OrderItem item)
{
if (item == null)
throw new ArgumentNullException(nameof(item));
if (_status != "PENDING")
throw new InvalidOperationException("Cannot modify non-pending order");
item.Validate(); // Item validates itself
_items.Add(item);
RecalculateTotal();
}
public void RemoveItem(OrderItem item)
{
if (_status != "PENDING")
throw new InvalidOperationException("Cannot modify non-pending order");
_items.Remove(item);
RecalculateTotal();
}
public void ApplyDiscount(decimal discountPercent, string authorizedBy)
{
if (discountPercent < 0 || discountPercent > 50)
throw new ArgumentException("Discount must be between 0 and 50%");
decimal discount = _totalAmount * (discountPercent / 100);
_totalAmount -= discount;
// Audit the discount
AuditLog.Record($"Discount of {discountPercent}% applied by {authorizedBy}");
}
public void MarkAsPaid(PaymentConfirmation confirmation)
{
if (confirmation == null)
throw new ArgumentNullException(nameof(confirmation));
if (!confirmation.IsValid)
throw new InvalidOperationException("Invalid payment confirmation");
if (confirmation.Amount < _totalAmount)
throw new InvalidOperationException("Payment amount insufficient");
_isPaid = true;
_status = "PAID";
}
private void RecalculateTotal()
{
_totalAmount = _items.Sum(i => i.Price * i.Quantity);
}
}
// Fixed: Processor uses Order's methods
public class FixedOrderProcessor
{
private readonly IPaymentGateway _paymentGateway;
public void ProcessPayment(FixedOrder order, PaymentDetails details)
{
// Use order's method to check state
if (order.IsPaid)
throw new InvalidOperationException("Order already paid");
// Process payment through gateway
var confirmation = _paymentGateway.Charge(details, order.TotalAmount);
// Use order's method to update state
order.MarkAsPaid(confirmation);
}
}
CVE Examples
This CWE is marked as PROHIBITED for direct CVE mapping as it represents a code quality/encapsulation concern rather than a direct security vulnerability.
Related CWEs
- CWE-1061: Insufficient Encapsulation (parent)
- CWE-1227: Encapsulation Issues (category member)
- CWE-766: Critical Data Element Declared Public (related)
References
- MITRE Corporation. "CWE-1090: Method Containing Access of a Member Element from Another Class." https://cwe.mitre.org/data/definitions/1090.html
- Martin, Robert C. "Clean Code" - Law of Demeter.
- Fowler, Martin. "TellDontAsk." https://martinfowler.com/bliki/TellDontAsk.html