Kritische öffentliche Variable ohne Final-Modifikator
Beschreibung
Kritische öffentliche Variable ohne Final-Modifikator ist eine Schwachstelle, bei der ein Produkt eine kritische öffentliche Variable enthält, der der final-Modifikator fehlt, was unbefugte Modifikation auf unerwartete Werte ermöglicht. Nicht-finale öffentliche Felder können von jedem Code geändert werden, der Zugriff auf die enthaltende Klasse hat. Dies birgt Risiken, wenn andere Programmkomponenten sich auf bestimmte Feldwerte für Sicherheitsentscheidungen, Konfiguration oder die Aufrechterhaltung eines konsistenten Zustands verlassen. Angreifer können diese Felder manipulieren, um das Programmverhalten zu ändern, Sicherheitsprüfungen zu umgehen oder den Anwendungszustand zu korrumpieren.
Risiko
Veränderliche öffentliche Felder in sicherheitskritischen Klassen erzeugen direkte Angriffsvektoren. Angreifer können Preisinformationen in E-Commerce-Anwendungen modifizieren, Berechtigungsstufen in Zugriffskontrollsystemen ändern, Konfigurationspfade auf bösartige Dateien umleiten oder Zustandsvariablen manipulieren, um Sicherheitsprüfungen zu umgehen. In Applet- oder verteilten Umgebungen kann jeder Code, der in derselben JVM läuft, diese Felder modifizieren. Das Risiko ist besonders hoch für Felder, die Sicherheitsrichtlinien, Finanzberechnungen, Dateipfade oder Authentifizierungszustände kontrollieren. Selbst scheinbar harmlose Felder können zu Angriffsvektoren werden, wenn ihre Modifikation die Programmlogik beeinflusst.
Lösung
Deklarieren Sie alle öffentlichen Felder als final wenn möglich, besonders in sicherheitssensitiven Klassen und Applets. Für Felder, die veränderlich sein müssen, machen Sie sie privat und bieten Sie kontrollierten Zugriff durch Getter- und Setter-Methoden mit Validierung. Führen Sie Plausibilitätsprüfungen durch, bevor Sie Werte aus öffentlichen Feldern verwenden. Erwägen Sie die Verwendung unveränderlicher Objekte für komplexe Datentypen. Verwenden Sie Kapselungsmuster zum Schutz kritischer Zustände. Erwägen Sie in Java defensives Kopieren, wenn Sie veränderliche Objekte durch Getter offenlegen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Integrität Anwendungsdaten modifizieren - Das Objekt könnte potenziell manipuliert werden, was Angreifern ermöglicht, kritische Werte zu modifizieren, die das Programmverhalten beeinflussen. |
| Vertraulichkeit | Umfang: Vertraulichkeit Anwendungsdaten lesen - Das Objekt könnte potenziell gelesen werden, was Angreifern ermöglicht, sensible Konfiguration oder Zustandsinformationen zu entdecken. |
| Zugriffskontrolle | Umfang: Zugriffskontrolle Schutzmechanismus umgehen - Modifizierung sicherheitsrelevanter Felder kann Authentifizierung, Autorisierung oder andere Sicherheitsprüfungen umgehen. |
Beispielcode
Anfälliger Code
// Anfällig: E-Commerce-Preisfeld nicht final
public class VulnerableProduct {
// Anfällig: Öffentlicher, nicht-finaler Preis kann modifiziert werden
public double price = 99.99;
public String name = "Premium Widget";
public int discountPercent = 0;
public double calculateTotal(int quantity) {
// Anfällig: Preis könnte modifiziert worden sein
double discount = price * discountPercent / 100.0;
return (price - discount) * quantity;
}
}
// Angreifer kann ausnutzen
public class PriceExploit {
public void exploit() {
VulnerableProduct product = new VulnerableProduct();
// Preis vor Kauf modifizieren
product.price = 0.01; // Fast kostenlos!
product.discountPercent = 99; // Maximaler Rabatt
double total = product.calculateTotal(1000);
System.out.println("Gesamt: " + total); // Zahlt fast nichts
}
}
// Anfällig: Konfiguration mit öffentlichen nicht-finalen Feldern
public class VulnerableConfig {
// Anfällig: Kritische Pfade können modifiziert werden
public String configPath = "/etc/application/config.dat";
public String logPath = "/var/log/application.log";
public String uploadDirectory = "/var/uploads/";
// Anfällig: Sicherheitseinstellungen können modifiziert werden
public boolean requireAuthentication = true;
public boolean enableEncryption = true;
public int maxLoginAttempts = 3;
}
// Angreifer leitet Konfiguration um
public class ConfigExploit {
public void exploit(VulnerableConfig config) {
// Config zu angreiferkontrollierter Datei umleiten
config.configPath = "/tmp/malicious-config.dat";
// Sicherheit deaktivieren
config.requireAuthentication = false;
config.enableEncryption = false;
config.maxLoginAttempts = Integer.MAX_VALUE;
// Uploads zu webzugänglichem Verzeichnis umleiten
config.uploadDirectory = "/var/www/html/uploads/";
}
}
// Anfällig: Applet mit veränderlichen öffentlichen Daten
import java.applet.Applet;
public class VulnerableApplet extends Applet {
// Anfällig: Öffentliche Felder im Applet
public String serverUrl = "https://secure-server.com/api";
public String apiKey = "geheimer-api-schlüssel-12345";
public boolean isAdmin = false;
public void makeRequest() {
// Anfällig: Verwendet potenziell modifizierte Werte
HttpClient.post(serverUrl, apiKey, getData());
if (isAdmin) {
// Angreifer kann Admin-Funktionen aktivieren
enableAdminFeatures();
}
}
}
// Anfällig: Sicherheitskontext mit veränderlichen Feldern
public class VulnerableSecurityContext {
// Anfällig: Authentifizierungszustand kann modifiziert werden
public boolean isAuthenticated = false;
public String userId = null;
public String[] roles = new String[0];
// Anfällig: Berechtigungsstufe kann modifiziert werden
public int permissionLevel = 0; // 0=keine, 1=lesen, 2=schreiben, 3=admin
public boolean canPerformAction(String action) {
// Entscheidung basiert auf potenziell modifizierten Feldern
if (!isAuthenticated) {
return false;
}
switch (action) {
case "read":
return permissionLevel >= 1;
case "write":
return permissionLevel >= 2;
case "admin":
return permissionLevel >= 3;
default:
return false;
}
}
}
// Angreifer eskaliert Privilegien
public class PrivilegeExploit {
public void exploit(VulnerableSecurityContext ctx) {
// Authentifiziert setzen ohne Anmeldung
ctx.isAuthenticated = true;
// Zu Admin eskalieren
ctx.permissionLevel = 3;
ctx.roles = new String[]{"ADMIN", "SUPER_USER"};
ctx.userId = "admin";
// Hat jetzt vollen Zugriff
boolean canAdmin = ctx.canPerformAction("admin"); // true!
}
}
// Anfällig: C++-Klasse mit öffentlichen nicht-const-Mitgliedern
class VulnerableAccount {
public:
// Anfällig: Öffentliche, nicht-const kritische Daten
double balance;
std::string accountNumber;
int accessLevel;
bool isLocked;
VulnerableAccount(std::string num) : accountNumber(num) {
balance = 0.0;
accessLevel = 1;
isLocked = false;
}
void withdraw(double amount) {
// Anfällig: balance könnte modifiziert worden sein
if (!isLocked && amount <= balance) {
balance -= amount;
}
}
};
// Angreifer modifiziert Konto
void exploit() {
VulnerableAccount account("12345");
// Angreifer modifiziert Kontostand direkt
account.balance = 1000000.0; // Kostenloses Geld!
account.accessLevel = 999; // Maximaler Zugriff
account.isLocked = false; // Entsperren falls gesperrt
}
Korrigierter Code
// Korrigiert: Unveränderliches Produkt mit finalen Feldern
public final class SecureProduct {
// Korrigiert: Finale Felder können nach Konstruktion nicht modifiziert werden
public final double price;
public final String name;
private final int discountPercent;
public SecureProduct(String name, double price, int discountPercent) {
if (price < 0) {
throw new IllegalArgumentException("Preis kann nicht negativ sein");
}
if (discountPercent < 0 || discountPercent > 100) {
throw new IllegalArgumentException("Ungültiger Rabatt");
}
this.name = name;
this.price = price;
this.discountPercent = discountPercent;
}
public double calculateTotal(int quantity) {
// Korrigiert: Preis ist unveränderlich
double discount = price * discountPercent / 100.0;
return (price - discount) * quantity;
}
public int getDiscountPercent() {
return discountPercent;
}
}
// Korrigiert: Konfiguration mit privaten Feldern und Validierung
public final class SecureConfig {
// Korrigiert: Private finale Felder
private final String configPath;
private final String logPath;
private final String uploadDirectory;
private final boolean requireAuthentication;
private final boolean enableEncryption;
private final int maxLoginAttempts;
private SecureConfig(Builder builder) {
this.configPath = builder.configPath;
this.logPath = builder.logPath;
this.uploadDirectory = builder.uploadDirectory;
this.requireAuthentication = builder.requireAuthentication;
this.enableEncryption = builder.enableEncryption;
this.maxLoginAttempts = builder.maxLoginAttempts;
}
// Korrigiert: Getter bieten nur Lesezugriff
public String getConfigPath() { return configPath; }
public String getLogPath() { return logPath; }
public String getUploadDirectory() { return uploadDirectory; }
public boolean isRequireAuthentication() { return requireAuthentication; }
public boolean isEnableEncryption() { return enableEncryption; }
public int getMaxLoginAttempts() { return maxLoginAttempts; }
// Korrigiert: Builder-Muster für kontrollierte Konstruktion
public static class Builder {
private String configPath = "/etc/application/config.dat";
private String logPath = "/var/log/application.log";
private String uploadDirectory = "/var/uploads/";
private boolean requireAuthentication = true;
private boolean enableEncryption = true;
private int maxLoginAttempts = 3;
public Builder configPath(String path) {
// Korrigiert: Pfad validieren
if (!isValidPath(path)) {
throw new IllegalArgumentException("Ungültiger Konfigurationspfad");
}
this.configPath = path;
return this;
}
public SecureConfig build() {
return new SecureConfig(this);
}
private boolean isValidPath(String path) {
// Validieren dass Pfad keine Traversierung enthält
return !path.contains("..") && path.startsWith("/etc/");
}
}
}
// Korrigiert: Sicheres Applet mit Kapselung
import java.applet.Applet;
public final class SecureApplet extends Applet {
// Korrigiert: Private finale Felder
private final String serverUrl;
private final String apiKey;
private boolean isAdmin; // Intern kontrolliert
public SecureApplet() {
// Korrigiert: Aus sicherer Quelle laden
this.serverUrl = loadServerUrl();
this.apiKey = loadApiKey();
this.isAdmin = false; // Standard auf Nicht-Admin
}
// Korrigiert: Keine Setter für kritische Felder
// Admin-Status durch Authentifizierung bestimmt, nicht öffentliches Feld
public void authenticate(String username, String password) {
// Korrigiert: isAdmin nur durch ordnungsgemäße Authentifizierung gesetzt
AuthResult result = AuthService.authenticate(username, password);
if (result.isSuccess()) {
this.isAdmin = result.hasAdminRole();
}
}
public boolean isAdmin() {
return isAdmin;
}
private String loadServerUrl() {
// Aus signierter Konfiguration laden
return SignedConfig.get("server.url");
}
private String loadApiKey() {
// Aus sicherem Speicher laden
return SecureStorage.getApiKey();
}
}
// Korrigiert: Unveränderlicher Sicherheitskontext
public final class SecureSecurityContext {
// Korrigiert: Alle Felder privat und final
private final boolean isAuthenticated;
private final String userId;
private final Set<String> roles;
private final int permissionLevel;
private SecureSecurityContext(String userId, Set<String> roles,
int permissionLevel) {
this.isAuthenticated = (userId != null);
this.userId = userId;
this.roles = Collections.unmodifiableSet(new HashSet<>(roles));
this.permissionLevel = permissionLevel;
}
// Korrigiert: Factory-Methode für nicht-authentifizierten Kontext
public static SecureSecurityContext anonymous() {
return new SecureSecurityContext(null, Collections.emptySet(), 0);
}
// Korrigiert: Factory-Methode erstellt Kontext aus Authentifizierungsergebnis
public static SecureSecurityContext fromAuthentication(AuthResult result) {
if (!result.isSuccess()) {
return anonymous();
}
return new SecureSecurityContext(
result.getUserId(),
result.getRoles(),
calculatePermissionLevel(result.getRoles())
);
}
public boolean canPerformAction(String action) {
if (!isAuthenticated) {
return false;
}
switch (action) {
case "read":
return permissionLevel >= 1;
case "write":
return permissionLevel >= 2;
case "admin":
return permissionLevel >= 3;
default:
return false;
}
}
// Korrigiert: Nur Getter, keine Setter
public boolean isAuthenticated() { return isAuthenticated; }
public String getUserId() { return userId; }
public Set<String> getRoles() { return roles; }
private static int calculatePermissionLevel(Set<String> roles) {
if (roles.contains("ADMIN")) return 3;
if (roles.contains("EDITOR")) return 2;
if (roles.contains("VIEWER")) return 1;
return 0;
}
}
// Korrigiert: C++-Klasse mit ordnungsgemäßer Kapselung
class SecureAccount {
private:
// Korrigiert: Private Mitglieder mit kontrolliertem Zugriff
double balance_;
const std::string accountNumber_;
int accessLevel_;
bool isLocked_;
public:
SecureAccount(const std::string& num)
: accountNumber_(num), balance_(0.0),
accessLevel_(1), isLocked_(false) {}
// Korrigiert: Kontrollierter Zugriff durch Methoden
double getBalance() const { return balance_; }
bool withdraw(double amount) {
if (isLocked_ || amount <= 0 || amount > balance_) {
return false;
}
balance_ -= amount;
auditLog("Abhebung", amount);
return true;
}
bool deposit(double amount) {
if (isLocked_ || amount <= 0) {
return false;
}
balance_ += amount;
auditLog("Einzahlung", amount);
return true;
}
// Korrigiert: Sperren/Entsperren erfordert Authentifizierung
bool lock(const AuthToken& token) {
if (!token.hasPermission("LOCK_ACCOUNTS")) {
return false;
}
isLocked_ = true;
return true;
}
private:
void auditLog(const std::string& action, double amount) {
// Transaktion für Audit protokollieren
}
};
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE aufgelistet. Das Schwachstellenmuster ist jedoch gut dokumentiert in:
- Java-Sicherheitsrichtlinien
- CERT Secure Coding Standards
Referenzen
- MITRE Corporation. "CWE-493: Critical Public Variable Without Final Modifier." https://cwe.mitre.org/data/definitions/493.html
- CERT Oracle Secure Coding Standard for Java. "OBJ10-J. Do not use public static nonfinal fields."
- Oracle. "Secure Coding Guidelines for Java SE."