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
| Impact | Details |
|---|---|
| Confidentiality | Scope: 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. |
| Integrity | Scope: 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
- MITRE Corporation. "CWE-487: Reliance on Package-level Scope." https://cwe.mitre.org/data/definitions/487.html
- Oracle. "Secure Coding Guidelines for Java SE."
- OWASP. "Java Security Cheat Sheet."