Reliance on Package-level Scope

Description

Reliance on Package-level Scope is a vulnerability where code incorrectly treats Java package-level access controls as security boundaries. Java packages are not inherently closed; they exist primarily as a convenience feature for developers to organize code and prevent accidental access, not as a security mechanism. Any code can declare itself to be part of any package, and since distributed Java code can be extended by classes in other JAR files claiming the same package, relying on package scope for security provides no real protection.

Risk

Package-level scope provides no security guarantees in Java applications. Attackers can create their own classes that declare themselves to be part of a target package, gaining access to all package-private members. This is particularly dangerous for sensitive data or methods that were assumed to be protected. In distributed environments, multiple JAR files can contribute classes to the same package, allowing malicious code to access supposedly protected members. The vulnerability is especially severe when package scope is used to protect security-critical data like credentials, cryptographic keys, or authorization tokens.

Solution

Never rely on package-level scope for security. Make sensitive data private and final whenever possible. Use proper encapsulation with private fields and controlled accessor methods. For security-critical code, use Java's SecurityManager and access control mechanisms. Consider using sealed packages (Java 15+) to prevent external code from joining the package. Implement proper authentication and authorization at the API level rather than relying on language-level access controls. Use encryption for sensitive data rather than depending on visibility modifiers.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Application Data - Since Java packages can be extended by external code, package-private data can be accessed by malicious classes declaring themselves part of the package.
IntegrityScope: Integrity

Modify Application Data - Package-private fields and methods can be accessed and modified by unauthorized code that joins the package.

Example Code

Vulnerable Code

// Vulnerable: Relying on package scope for security
package com.banking.security;

// Vulnerable: Package-private class assumed to be protected
class InternalSecurityManager {
    // Vulnerable: Package-private field assumed safe
    static String masterKey = "super-secret-key-12345";

    // Vulnerable: Package-private method assumed protected
    static boolean validateAdminAccess(String token) {
        return token.equals(masterKey);
    }

    // Package-private utility - attacker can call this
    static void resetAllSessions() {
        // Dangerous operation assumed protected by package scope
        SessionStore.invalidateAll();
    }
}

// Vulnerable: Sensitive data with package scope
package com.banking.data;

class CustomerDatabase {
    // Vulnerable: Package scope for sensitive data
    String[] customerSSNs = new String[1000];
    String[] accountNumbers = new String[1000];

    // Vulnerable: Package-private method exposes data
    String getSSN(int customerId) {
        return customerSSNs[customerId];
    }
}
// Attacker's code - can declare itself part of the same package
package com.banking.security;  // Same package as target!

public class MaliciousAccessor {

    public static void exploit() {
        // Attacker can access package-private members
        System.out.println("Master key: " + InternalSecurityManager.masterKey);

        // Attacker can call package-private methods
        InternalSecurityManager.resetAllSessions();

        // Attacker can forge admin access
        boolean hasAccess = InternalSecurityManager.validateAdminAccess(
            InternalSecurityManager.masterKey
        );
    }
}
// Vulnerable: Configuration with package-private fields
package com.app.config;

public class AppConfiguration {
    // Vulnerable: Package scope for sensitive config
    String databasePassword = "db_password_123";
    String apiSecret = "api_secret_key";

    // Public method but relies on callers being in same package
    // to access internal fields
    public void initialize() {
        // Assumes only trusted code can see fields
        connectToDatabase(databasePassword);
        initializeApi(apiSecret);
    }
}

// Vulnerable: Security check relying on package scope
class SecurityValidator {
    // Package-private - assumed only internal classes can call
    boolean bypassSecurityCheck = false;

    public boolean isSecure(Request request) {
        // Vulnerable: bypassSecurityCheck can be set by attacker
        if (bypassSecurityCheck) {
            return true;  // Attacker can bypass all checks
        }
        return performActualValidation(request);
    }
}

Fixed Code

// Fixed: Proper encapsulation without relying on package scope
package com.banking.security;

public final class SecureSecurityManager {
    // Fixed: Private, final field - cannot be accessed or modified
    private static final String masterKey;

    static {
        // Fixed: Load from secure source, not hardcoded
        masterKey = loadKeyFromSecureStore();
    }

    // Fixed: No direct access to master key
    // Use secure comparison method instead
    public static boolean validateAdminAccess(String token) {
        // Fixed: Constant-time comparison to prevent timing attacks
        return MessageDigest.isEqual(
            token.getBytes(StandardCharsets.UTF_8),
            masterKey.getBytes(StandardCharsets.UTF_8)
        );
    }

    // Fixed: Require proper authorization for sensitive operations
    public static void resetAllSessions(AdminCredentials creds) {
        if (!validateAdminCredentials(creds)) {
            throw new SecurityException("Unauthorized");
        }
        // Audit log before dangerous operation
        auditLog("Session reset by: " + creds.getAdminId());
        SessionStore.invalidateAll();
    }

    private static String loadKeyFromSecureStore() {
        // Load from HSM, encrypted config, or secure vault
        return SecureKeyVault.getKey("master-key");
    }

    private static boolean validateAdminCredentials(AdminCredentials creds) {
        // Proper authentication check
        return AuthenticationService.validate(creds);
    }
}
// Fixed: Proper data encapsulation
package com.banking.data;

public final class SecureCustomerDatabase {
    // Fixed: Private fields with no direct access
    private final String[] customerSSNs;
    private final String[] accountNumbers;

    public SecureCustomerDatabase(int capacity) {
        this.customerSSNs = new String[capacity];
        this.accountNumbers = new String[capacity];
    }

    // Fixed: Controlled access with authorization check
    public String getSSN(int customerId, UserContext context) {
        // Fixed: Verify authorization before returning sensitive data
        if (!AuthorizationService.canAccessSSN(context, customerId)) {
            throw new SecurityException("Not authorized to access SSN");
        }

        // Fixed: Audit sensitive data access
        AuditLog.logDataAccess(context.getUserId(), "SSN", customerId);

        // Fixed: Return masked data unless full access granted
        if (context.hasFullSSNAccess()) {
            return customerSSNs[customerId];
        } else {
            return maskSSN(customerSSNs[customerId]);
        }
    }

    private String maskSSN(String ssn) {
        return "XXX-XX-" + ssn.substring(ssn.length() - 4);
    }
}
// Fixed: Secure configuration management
package com.app.config;

public final class SecureAppConfiguration {
    // Fixed: Private, final, and loaded securely
    private final String databasePassword;
    private final String apiSecret;

    // Fixed: Use secure configuration loading
    private SecureAppConfiguration() {
        // Load from encrypted configuration
        EncryptedConfig config = EncryptedConfig.load();
        this.databasePassword = config.getDecrypted("db.password");
        this.apiSecret = config.getDecrypted("api.secret");
    }

    // Fixed: Singleton with proper synchronization
    private static volatile SecureAppConfiguration instance;

    public static SecureAppConfiguration getInstance() {
        if (instance == null) {
            synchronized (SecureAppConfiguration.class) {
                if (instance == null) {
                    instance = new SecureAppConfiguration();
                }
            }
        }
        return instance;
    }

    // Fixed: No getters for sensitive data
    // Instead, provide functional methods that use the data internally
    public Connection getDatabaseConnection() {
        return DatabasePool.getConnection(databasePassword);
    }

    public ApiClient getApiClient() {
        return new ApiClient(apiSecret);
    }
}

// Fixed: Immutable security validator
public final class SecureSecurityValidator {
    // Fixed: No mutable bypass flag

    public boolean isSecure(Request request) {
        // Fixed: Always perform validation, no bypass mechanism
        return performActualValidation(request);
    }

    private boolean performActualValidation(Request request) {
        return checkAuthentication(request) &&
               checkAuthorization(request) &&
               checkInputValidation(request) &&
               checkRateLimiting(request);
    }
}
// Fixed: Using Java 15+ sealed classes for stronger encapsulation
package com.banking.security;

// Fixed: Sealed class hierarchy - only permitted subclasses
public sealed class SecurityToken
    permits AdminToken, UserToken, ServiceToken {

    private final String tokenValue;
    private final Instant expiration;

    protected SecurityToken(String tokenValue, Instant expiration) {
        this.tokenValue = tokenValue;
        this.expiration = expiration;
    }

    public boolean isValid() {
        return Instant.now().isBefore(expiration);
    }
}

// Only these classes can extend SecurityToken
public final class AdminToken extends SecurityToken {
    private final Set<Permission> permissions;

    public AdminToken(String token, Instant exp, Set<Permission> perms) {
        super(token, exp);
        this.permissions = Set.copyOf(perms);  // Immutable copy
    }
}

CVE Examples

No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:

  • Java security best practices documentation
  • Seven Pernicious Kingdoms taxonomy

References

  1. MITRE Corporation. "CWE-487: Reliance on Package-level Scope." https://cwe.mitre.org/data/definitions/487.html
  2. Oracle. "Secure Coding Guidelines for Java SE."
  3. OWASP. "Java Security Cheat Sheet."