Use of Inner Class Containing Sensitive Data
Description
Use of Inner Class Containing Sensitive Data is a vulnerability where inner classes are used to hold or access sensitive information. Java's compiler transforms inner classes into separate peer classes with package-level access, potentially exposing code and data the programmer intended to keep private. When inner classes access private fields in their enclosing class, the compiler may convert those private fields into protected or package-private fields, creating security vulnerabilities. This occurs because Java bytecode has no concept of inner classes - they are purely a source-level construct.
Risk
Inner classes create hidden security risks because their compiled form differs significantly from their source representation. Private data accessed by inner classes becomes accessible to any class in the same package. Attackers can create classes in the same package to access supposedly private fields and methods exposed through inner class compilation. Sensitive data such as credentials, cryptographic keys, or personal information stored in inner classes or accessed by them from enclosing classes can be read or modified by malicious code. The risk is amplified because developers often assume inner class access modifiers provide security guarantees they do not actually provide.
Solution
Avoid using inner classes to store or access sensitive data. Use sealed classes where available to protect encapsulation. Make inner classes static when they don't need access to enclosing class instance members, as static inner classes have more predictable access semantics. Consider using local inner classes defined within methods when the class is only needed in a specific scope. For truly sensitive operations, use separate classes with proper encapsulation rather than relying on inner class access restrictions. Never assume that inner classes provide security isolation.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Inner class data confidentiality can be overcome, allowing attackers to read sensitive application data through the exposed package-level access. |
| Integrity | Scope: Integrity Modify Application Data - Attackers can modify data in inner classes or data accessed by inner classes from outer classes through the exposed access paths. |
Example Code
Vulnerable Code
// Vulnerable: Inner class accessing private sensitive data
public class VulnerableBankingSystem {
// Private field - but inner class access makes it package-accessible
private String masterEncryptionKey = "super-secret-key-12345";
private Map<String, Double> accountBalances = new HashMap<>();
// Vulnerable: Non-static inner class has access to all private members
public class TransactionProcessor {
public void processTransaction(String accountId, double amount) {
// Inner class can access private outer class fields
// After compilation, masterEncryptionKey becomes package-accessible
String encrypted = encrypt(amount, masterEncryptionKey);
// accountBalances is also exposed
double currentBalance = accountBalances.get(accountId);
accountBalances.put(accountId, currentBalance + amount);
}
private String encrypt(double amount, String key) {
// Encryption logic
return "" + amount;
}
}
}
// Attacker in same package can exploit the exposed fields
package com.banking; // Same package as VulnerableBankingSystem
public class Attacker {
public void exploit() {
VulnerableBankingSystem system = new VulnerableBankingSystem();
// Due to inner class compilation, private fields are now accessible
// through reflection or synthetic accessor methods
try {
Field keyField = VulnerableBankingSystem.class
.getDeclaredField("masterEncryptionKey");
keyField.setAccessible(true); // Made easier by inner class access
String key = (String) keyField.get(system);
System.out.println("Stolen key: " + key);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// Vulnerable: Applet with inner class exposing sensitive data
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class VulnerableApplet extends Applet {
// Private sensitive data
private String userPassword;
private String sessionToken;
// Vulnerable: Anonymous inner class for event handling
// accesses private fields, exposing them at bytecode level
public void init() {
Button submitButton = new Button("Submit");
// Vulnerable: Anonymous inner class
submitButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// Accessing private field exposes it in bytecode
authenticateUser(userPassword);
// sessionToken also becomes accessible
startSession(sessionToken);
}
});
add(submitButton);
}
private void authenticateUser(String password) {
// Authentication logic
}
private void startSession(String token) {
// Session logic
}
}
// Vulnerable: Class with sensitive inner class data
public class VulnerableUserManager {
// Vulnerable: Inner class holding sensitive user data
private class UserCredentials {
String username;
String password;
String ssn;
String creditCardNumber;
UserCredentials(String user, String pass, String ssn, String cc) {
this.username = user;
this.password = pass;
this.ssn = ssn;
this.creditCardNumber = cc;
}
}
private UserCredentials currentUser;
public void login(String user, String pass, String ssn, String cc) {
// Vulnerable: Sensitive data in inner class
// is more accessible than it appears in source
currentUser = new UserCredentials(user, pass, ssn, cc);
}
}
Fixed Code
// Fixed: Use static inner class or separate class
public class SecureBankingSystem {
private final String masterEncryptionKey;
private final Map<String, Double> accountBalances;
public SecureBankingSystem() {
this.masterEncryptionKey = loadKeyFromSecureStorage();
this.accountBalances = new ConcurrentHashMap<>();
}
// Fixed: Static inner class doesn't have implicit access to outer class
public static class TransactionProcessor {
private final EncryptionService encryptionService;
private final AccountRepository accountRepository;
public TransactionProcessor(EncryptionService encryption,
AccountRepository accounts) {
this.encryptionService = encryption;
this.accountRepository = accounts;
}
public void processTransaction(String accountId, double amount) {
// Fixed: Uses injected dependencies, not outer class fields
String encrypted = encryptionService.encrypt(amount);
accountRepository.updateBalance(accountId, amount);
}
}
// Fixed: Factory method provides controlled access
public TransactionProcessor createProcessor() {
return new TransactionProcessor(
new EncryptionService(masterEncryptionKey),
new AccountRepository(accountBalances)
);
}
private String loadKeyFromSecureStorage() {
// Load from HSM or secure vault
return SecureKeyVault.getKey("master-key");
}
}
// Fixed: Use local inner class for limited scope
public class SecureApplet extends Applet {
private char[] userPassword; // char[] for passwords, not String
public void init() {
Button submitButton = new Button("Submit");
// Fixed: Method handles authentication directly
submitButton.addActionListener(this::handleSubmit);
add(submitButton);
}
// Fixed: Implement ActionListener directly on the class
private void handleSubmit(ActionEvent e) {
try {
// Process password locally
authenticateUser(userPassword);
} finally {
// Clear sensitive data after use
Arrays.fill(userPassword, '\0');
}
}
private void authenticateUser(char[] password) {
// Authentication logic
}
}
// Alternative: Implement interface directly
public class SecureApplet2 extends Applet implements ActionListener {
private char[] userPassword;
public void init() {
Button submitButton = new Button("Submit");
submitButton.addActionListener(this);
add(submitButton);
}
@Override
public void actionPerformed(ActionEvent e) {
// Fixed: No inner class, direct implementation
authenticateUser(userPassword);
}
private void authenticateUser(char[] password) {
// Authentication logic
}
}
// Fixed: Separate class for sensitive data with proper encapsulation
public final class SecureUserManager {
// Fixed: Credentials stored in separate, sealed class
private SecureCredentials currentUser;
public void login(String user, char[] pass) {
// Fixed: Use properly encapsulated credential object
currentUser = SecureCredentials.create(user, pass);
// Clear password array after use
Arrays.fill(pass, '\0');
}
public boolean isAuthenticated() {
return currentUser != null && currentUser.isValid();
}
}
// Fixed: Separate final class for credentials (not an inner class)
public final class SecureCredentials {
private final String username;
private final byte[] hashedPassword;
private final Instant createdAt;
private SecureCredentials(String username, byte[] hashedPassword) {
this.username = username;
this.hashedPassword = hashedPassword;
this.createdAt = Instant.now();
}
public static SecureCredentials create(String username, char[] password) {
byte[] hashed = hashPassword(password);
// Clear original password
Arrays.fill(password, '\0');
return new SecureCredentials(username, hashed);
}
public boolean isValid() {
// Check if credentials haven't expired
return createdAt.plusSeconds(3600).isAfter(Instant.now());
}
public boolean verifyPassword(char[] password) {
byte[] hashed = hashPassword(password);
boolean matches = MessageDigest.isEqual(hashedPassword, hashed);
Arrays.fill(hashed, (byte) 0);
return matches;
}
private static byte[] hashPassword(char[] password) {
// Use proper password hashing (bcrypt, Argon2, etc.)
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] bytes = new String(password).getBytes(StandardCharsets.UTF_8);
byte[] hash = md.digest(bytes);
Arrays.fill(bytes, (byte) 0);
return hash;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
CVE Examples
No specific CVEs are listed in the MITRE database for this CWE. However, the vulnerability pattern is documented in:
- Seven Pernicious Kingdoms taxonomy
- Java bytecode security research
References
- MITRE Corporation. "CWE-492: Use of Inner Class Containing Sensitive Data." https://cwe.mitre.org/data/definitions/492.html
- Oracle. "Secure Coding Guidelines for Java SE."
- McGraw, Gary and Felten, Edward. "Securing Java."