Öffentliches statisches Feld nicht als final markiert

Beschreibung

Öffentliches statisches Feld nicht als final markiert ist eine Schwachstelle, bei der ein Objekt ein öffentliches statisches Feld enthält, das nicht als final markiert ist, wodurch es auf unerwartete Weise modifiziert werden kann. Öffentliche statische Variablen können von jedem Code in der Anwendung aufgerufen und geändert werden, ohne dass Accessor- oder Mutator-Methoden erforderlich sind. Dies erzeugt einen globalen veränderbaren Zustand, der von bösartigem Code ausgenutzt oder versehentlich von gut gemeintem, aber fehlerhaftem Code korrumpiert werden kann. Das Fehlen des final-Modifikators bedeutet, dass sich der Feldwert jederzeit von überall im Programm ändern kann.

Risiko

Nicht-finale öffentliche statische Felder erzeugen einen globalen veränderbaren Zustand, der für allen Code in der Anwendung zugänglich ist. Angreifer können Konfigurationspfade modifizieren, um auf bösartige Dateien zu verweisen, Sicherheitseinstellungen ändern, um Schutzmaßnahmen zu deaktivieren, Preis- oder Geschäftslogikwerte ändern oder den gemeinsamen Zustand korrumpieren, um Anwendungsausfälle zu verursachen. In Multithread-Umgebungen können Race Conditions bei solchen Feldern zu inkonsistentem Verhalten führen. In Umgebungen mit gemeinsamen Classloadern wie Anwendungsservern können Modifikationen andere Anwendungen beeinflussen. Das Risiko wird verstärkt, weil Änderungen nicht auditiert werden und von überall in der Codebasis erfolgen können.

Lösung

Markieren Sie alle öffentlichen statischen Felder als final, wenn möglich. Für Konstanten verwenden Sie public static final, um wirklich unveränderliche Werte zu erstellen. Wenn ein Feld statisch sein muss, aber nicht global modifizierbar sein sollte, machen Sie es privat und bieten Sie kontrollierten Zugriff über statische Getter-Methoden. Für Konfiguration, die zur Laufzeit geändert werden muss, verwenden Sie ordnungsgemäßes Konfigurationsmanagement mit Zugriffskontrollen anstelle von öffentlichen statischen Feldern. In C/C++ verwenden Sie das const-Schlüsselwort für konstante Werte. Wenden Sie defensive Programmierung an, indem Sie Werte vor der Verwendung validieren, selbst wenn sie konstant sein sollten.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Anwendungsdaten modifizieren - Das Objekt könnte potenziell manipuliert werden, was Angreifern ermöglicht, Konfiguration, Sicherheitseinstellungen oder Geschäftslogikwerte zu ändern.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Das Objekt könnte potenziell gelesen werden, was sensible Konfigurationsinformationen oder den Anwendungszustand offenlegt.
ZugriffskontrolleBereich: Zugriffskontrolle

Schutzmechanismus umgehen - Das Modifizieren statischer sicherheitsrelevanter Felder kann Authentifizierung, Autorisierung oder andere Sicherheitsüberprüfungen umgehen.

Beispielcode

Verwundbarer Code

// Verwundbar: Öffentliches statisches Feld ohne final
public class VulnerableConfiguration {
    // Verwundbar: Kann von überall modifiziert werden
    public static String appPropertiesFile = "app/Application.properties";
    public static String databaseUrl = "jdbc:mysql://localhost:3306/mydb";
    public static String logDirectory = "/var/log/app/";
    public static int maxLoginAttempts = 3;
    public static boolean debugMode = false;
}

// Angreifer nutzt veränderbare statische Felder aus
public class ConfigExploit {
    public void exploit() {
        // Konfiguration auf angreiferkontrollierte Datei umleiten
        VulnerableConfiguration.appPropertiesFile = "/tmp/malicious.properties";

        // Debug-Modus aktivieren, um sensible Infos offenzulegen
        VulnerableConfiguration.debugMode = true;

        // Unbegrenzte Login-Versuche für Brute-Force erlauben
        VulnerableConfiguration.maxLoginAttempts = Integer.MAX_VALUE;

        // Logs an sichtbaren Ort umleiten
        VulnerableConfiguration.logDirectory = "/var/www/html/logs/";
    }
}

// Verwundbar: Sicherheitseinstellungen als nicht-finale Statics
public class VulnerableSecuritySettings {
    // Verwundbar: Sicherheitskontrollen können deaktiviert werden
    public static boolean requireHttps = true;
    public static boolean validateCertificates = true;
    public static boolean enforceAuthentication = true;
    public static String[] allowedOrigins = {"https://trusted.com"};

    // Verwundbar: Verschlüsselungseinstellungen modifizierbar
    public static String encryptionAlgorithm = "AES-256";
    public static int keySize = 256;
}

// Angreifer schwächt Sicherheit
public class SecurityExploit {
    public void weaken() {
        // HTTPS-Anforderung deaktivieren
        VulnerableSecuritySettings.requireHttps = false;

        // Zertifikatsvalidierung deaktivieren (MITM-Angriffsvektor)
        VulnerableSecuritySettings.validateCertificates = false;

        // Authentifizierung deaktivieren
        VulnerableSecuritySettings.enforceAuthentication = false;

        // Jeden Origin erlauben
        VulnerableSecuritySettings.allowedOrigins = new String[]{"*"};

        // Verschlüsselung abschwächen
        VulnerableSecuritySettings.encryptionAlgorithm = "DES";
        VulnerableSecuritySettings.keySize = 56;
    }
}

// Verwundbar: Preise und Geschäftslogik
public class VulnerablePricing {
    // Verwundbar: Preise können modifiziert werden
    public static double taxRate = 0.08;
    public static double shippingCost = 9.99;
    public static double discountMax = 0.20;  // 20% max Rabatt
    public static double premiumSubscriptionPrice = 99.99;
}

// Angreifer manipuliert Preise
public class PricingExploit {
    public void exploit() {
        VulnerablePricing.taxRate = 0.0;        // Keine Steuer!
        VulnerablePricing.shippingCost = 0.0;   // Kostenloser Versand!
        VulnerablePricing.discountMax = 1.0;    // 100% Rabatt!
        VulnerablePricing.premiumSubscriptionPrice = 0.01;  // Fast kostenlos!
    }
}
// Verwundbar: C++ öffentlich statisch nicht-const
class VulnerableSettings {
public:
    // Verwundbar: Von überall modifizierbar
    static char* configFile;
    static int maxConnections;
    static bool securityEnabled;
};

// Statische Member initialisieren
char* VulnerableSettings::configFile = "/etc/app/config.cfg";
int VulnerableSettings::maxConnections = 100;
bool VulnerableSettings::securityEnabled = true;

// Ausnutzung
void exploit() {
    VulnerableSettings::configFile = "/tmp/evil.cfg";
    VulnerableSettings::securityEnabled = false;
    VulnerableSettings::maxConnections = 10000;  // DoS
}
// Verwundbar: Applet mit veränderbarem statischem Zustand
import java.applet.Applet;

public class VulnerableApplet extends Applet {
    // Verwundbar: Andere Applets können diese modifizieren
    public static String serverEndpoint = "https://api.trusted.com";
    public static String apiKey = "secret-key-12345";
    public static int[] allowedPorts = {443, 8443};

    public void init() {
        // Verwendet potenziell modifizierte Werte
        connectToServer(serverEndpoint, apiKey);
    }
}

// Bösartiges Applet in derselben Umgebung
public class MaliciousApplet extends Applet {
    public void init() {
        // Datenverkehr über Angreiferserver umleiten
        VulnerableApplet.serverEndpoint = "https://attacker.com/proxy";

        // API-Schlüssel ersetzen
        VulnerableApplet.apiKey = "attacker-key";
    }
}

Lösungscode

// Behoben: Final für Konstanten verwenden
public final class SecureConfiguration {
    // Behoben: Privater Konstruktor verhindert Instanziierung
    private SecureConfiguration() {}

    // Behoben: Final-Felder können nicht modifiziert werden
    public static final String APP_PROPERTIES_FILE = "app/Application.properties";
    public static final String LOG_DIRECTORY = "/var/log/app/";
    public static final int MAX_LOGIN_ATTEMPTS = 3;

    // Behoben: Für Laufzeitkonfiguration ordentlichen Konfigurationsdienst verwenden
    private static volatile Configuration runtimeConfig = loadConfiguration();

    // Behoben: Kontrollierter Zugriff auf veränderbare Konfiguration
    public static Configuration getRuntimeConfig() {
        return runtimeConfig;  // Unveränderlich oder Kopie zurückgeben
    }

    // Behoben: Kontrollierte Updates mit Autorisierung
    public static void updateConfig(Configuration newConfig, AdminToken token) {
        if (!token.isValid() || !token.hasPermission("UPDATE_CONFIG")) {
            throw new SecurityException("Nicht autorisiertes Konfigurationsupdate");
        }
        auditLog("Konfiguration aktualisiert von: " + token.getAdmin());
        runtimeConfig = newConfig;
    }

    private static Configuration loadConfiguration() {
        return ConfigurationLoader.load(APP_PROPERTIES_FILE);
    }
}

// Behoben: Unveränderliche Sicherheitseinstellungen
public final class SecureSecuritySettings {
    private SecureSecuritySettings() {}

    // Behoben: Alle Einstellungen final
    public static final boolean REQUIRE_HTTPS = true;
    public static final boolean VALIDATE_CERTIFICATES = true;
    public static final boolean ENFORCE_AUTHENTICATION = true;

    // Behoben: Unveränderliche Liste für erlaubte Origins
    public static final List<String> ALLOWED_ORIGINS =
        Collections.unmodifiableList(Arrays.asList("https://trusted.com"));

    // Behoben: Finale Verschlüsselungseinstellungen
    public static final String ENCRYPTION_ALGORITHM = "AES-256-GCM";
    public static final int KEY_SIZE = 256;

    // Behoben: Wenn Einstellungen konfigurierbar sein müssen, sichere Konfiguration verwenden
    private static final SecurityConfig config = loadSecurityConfig();

    public static SecurityConfig getConfig() {
        return config;  // Unveränderliches Config-Objekt
    }

    private static SecurityConfig loadSecurityConfig() {
        // Aus sicherer, signierter Konfiguration laden
        return SignedConfigLoader.loadSecurityConfig();
    }
}

// Behoben: Unveränderliche Preiskonfiguration
public final class SecurePricing {
    private SecurePricing() {}

    // Behoben: Geschäftsregeln als finale Konstanten
    public static final double DEFAULT_TAX_RATE = 0.08;
    public static final double BASE_SHIPPING_COST = 9.99;
    public static final double MAX_DISCOUNT_PERCENT = 0.20;
    public static final double PREMIUM_SUBSCRIPTION_BASE = 99.99;

    // Behoben: Dynamische Preisgestaltung durch kontrollierten Dienst
    private static final PricingService pricingService = new PricingService();

    public static PricingService getPricingService() {
        return pricingService;
    }

    // Behoben: Preise können nur durch auditierten Dienst aktualisiert werden
    public static class PricingService {
        private volatile PricingRules rules;

        public PricingService() {
            this.rules = loadFromDatabase();
        }

        public double getTaxRate(String region) {
            return rules.getTaxRate(region);
        }

        public void updateRules(PricingRules newRules, AdminCredentials creds) {
            if (!validateAdmin(creds)) {
                throw new SecurityException("Nicht autorisiert");
            }
            auditLog("Preise aktualisiert von: " + creds.getAdminId());
            this.rules = newRules;
        }
    }
}
// Behoben: C++ mit const und kontrolliertem Zugriff
class SecureSettings {
public:
    // Behoben: const static Member
    static const char* const CONFIG_FILE;
    static const int MAX_CONNECTIONS;
    static const bool SECURITY_ENABLED;

    // Behoben: Für veränderbare Einstellungen kontrollierten Zugriff verwenden
    static int getCurrentMaxConnections() {
        return currentMaxConnections;
    }

    static bool setMaxConnections(int value, const AdminToken& token) {
        if (!token.isValid()) {
            return false;
        }
        currentMaxConnections = value;
        return true;
    }

private:
    static int currentMaxConnections;
};

// Const static Member initialisieren
const char* const SecureSettings::CONFIG_FILE = "/etc/app/config.cfg";
const int SecureSettings::MAX_CONNECTIONS = 100;
const bool SecureSettings::SECURITY_ENABLED = true;
int SecureSettings::currentMaxConnections = 100;
// Behoben: Sichere Applet-Konfiguration
import java.applet.Applet;

public final class SecureApplet extends Applet {
    // Behoben: Finale Konstanten
    private static final String DEFAULT_SERVER = "https://api.trusted.com";

    // Behoben: Konfiguration einmal geladen, nicht modifizierbar
    private final String serverEndpoint;
    private final String apiKey;
    private final int[] allowedPorts;

    public SecureApplet() {
        // Behoben: Aus signierter Konfiguration laden
        SignedConfig config = SignedConfig.load();
        this.serverEndpoint = config.getString("server", DEFAULT_SERVER);
        this.apiKey = config.getEncrypted("apiKey");
        this.allowedPorts = config.getIntArray("ports").clone();
    }

    @Override
    public void init() {
        // Behoben: Verwendet Instanzfelder, nicht statisch veränderbare Felder
        connectToServer(serverEndpoint, apiKey);
    }

    // Behoben: Defensive Kopie für Array-Zugriff
    public int[] getAllowedPorts() {
        return allowedPorts.clone();
    }
}

CVE-Beispiele

Keine spezifischen CVEs sind in der MITRE-Datenbank für diese CWE aufgeführt. Das Schwachstellenmuster ist jedoch dokumentiert in:

  • CERT Oracle Secure Coding Standard for Java (OBJ10-J)
  • Java-Sicherheitsrichtlinien

Referenzen

  1. MITRE Corporation. "CWE-500: Public Static Field Not Marked Final." https://cwe.mitre.org/data/definitions/500.html
  2. CERT Oracle Secure Coding Standard for Java. "OBJ10-J. Do not use public static nonfinal fields."
  3. Oracle. "Secure Coding Guidelines for Java SE."