Immer-fehlerhafte Kontrollfluss-Implementierung

Beschreibung

Immer-fehlerhafte Kontrollfluss-Implementierung tritt auf, wenn Code Logik enthält, die unabhängig von Eingabe oder Programmzustand immer falsches Verhalten erzeugt. Dies umfasst unmögliche Bedingungen, unerreichbare Code-Pfade, Bedingungen die nie wahr/falsch sein können, und Logikfehler die Sicherheitsprüfungen unwirksam machen. Im Gegensatz zu bedingten Fehlern, die nur unter bestimmten Umständen auftreten, sind diese Mängel grundlegend in allen Fällen fehlerhaft.

Risiko

Sicherheitsprüfungen, die nie ausgeführt werden. Authentifizierungsumgehung wenn Bedingungen invertiert sind. Autorisierung wird immer falsch gewährt oder verweigert. Kritische Code-Pfade werden nie erreicht. Fehlerbehandlung, die nie auslöst. Validierung, die immer besteht oder fehlschlägt, unabhängig von der Eingabe.

Lösung

Verwenden Sie statische Analyse, um toten Code und unmögliche Bedingungen zu erkennen. Überprüfen Sie den Kontrollfluss auf logische Korrektheit. Testen Sie sowohl positive als auch negative Pfade. Verwenden Sie Code-Coverage, um unerreichbaren Code zu finden. Implementieren Sie ordnungsgemäße boolesche Logik in Sicherheitsprüfungen. Überprüfen Sie Vergleiche auf Typfehler.

Häufige Konsequenzen

AuswirkungDetails
AutorisierungBereich: Vollständige Umgehung

Sicherheitsprüfungen scheitern oder gelingen immer.
IntegritätBereich: Logikfehler

Code verhält sich nie wie beabsichtigt.
VerfügbarkeitBereich: Funktionalität defekt

Erwartete Features funktionieren nie.

Beispielcode + Lösungscode

Verwundbarer Code

// VERWUNDBAR: Immer-fehlerhafter Kontrollfluss in C
#include <stdio.h>
#include <string.h>

// VERWUNDBAR: Bedingung ist immer wahr
int check_auth_vulnerable1(char* password) {
    // VERWUNDBAR: Zuweisung statt Vergleich
    if (password = "secret") {  // Immer wahr (Nicht-Null Pointer)
        return 1;  // Immer authentifiziert!
    }
    return 0;  // Nie erreicht
}

// VERWUNDBAR: Bedingung ist immer falsch
int check_auth_vulnerable2(char* password) {
    // VERWUNDBAR: Pointer verglichen, nicht Strings
    if (password == "secret") {  // Fast immer falsch
        return 1;  // Nie erreicht außer bei gleichem Literal
    }
    return 0;  // Immer zurückgegeben
}

// VERWUNDBAR: Unsigned-Vergleich immer wahr
int validate_index_vulnerable(unsigned int index) {
    // VERWUNDBAR: Unsigned ist immer >= 0
    if (index >= 0) {  // Immer wahr
        return 1;  // Immer gültig!
    }
    return 0;  // Nie erreicht
}

// VERWUNDBAR: Logikfehler mit return
int process_vulnerable(int value) {
    if (value > 0) {
        return 1;
    }

    // VERWUNDBAR: Frühes return macht dies unerreichbar
    return 0;

    // Diese Sicherheitsprüfung wird nie ausgeführt
    if (!validate_value(value)) {
        log_error("Ungültiger Wert");
        return -1;
    }
}

// VERWUNDBAR: Semikolon nach if
void security_check_vulnerable(int user_level) {
    if (user_level < ADMIN_LEVEL);  // VERWUNDBAR: Semikolon!
    {
        // Dieser Block wird immer ausgeführt
        grant_admin_access();
    }
}

// VERWUNDBAR: Bitweise vs. logischer Operator
int check_permissions_vulnerable(int perm1, int perm2) {
    // VERWUNDBAR: & statt &&
    if (perm1 & perm2) {  // Bitweises UND, nicht logisch
        // Funktioniert möglicherweise nicht wie erwartet
        return 1;
    }
    return 0;
}
# VERWUNDBAR: Python immer-fehlerhafter Kontrollfluss
class VulnerableAuth:

    # VERWUNDBAR: Immer-True-Bedingung
    def check_password_vulnerable1(self, password):
        # VERWUNDBAR: 'is' vergleicht Identität, nicht Gleichheit
        # String-Literale können interniert sein oder nicht
        if password is "secret":  # Normalerweise False
            return True
        return False  # Fast immer zurückgegeben

    # VERWUNDBAR: Falsches return-Platzierung
    def validate_vulnerable(self, data):
        return True  # VERWUNDBAR: Gibt immer zuerst True zurück

        # Diese Validierung läuft nie
        if not self.is_valid(data):
            raise ValueError("Ungültige Daten")

    # VERWUNDBAR: Leerer Bedingungsblock
    def process_vulnerable(self, value):
        if value < 0:
            pass  # VERWUNDBAR: Macht nichts
            # Negative Werte nicht behandelt!

        self.process_value(value)  # Verarbeitet negative Werte

    # VERWUNDBAR: Veränderliches Standard-Argument
    def add_permission_vulnerable(self, perm, perms=[]):
        # VERWUNDBAR: perms zwischen allen Aufrufen geteilt
        perms.append(perm)
        return perms  # Akkumuliert über Aufrufe hinweg!

    # VERWUNDBAR: Boolean-Short-Circuit-Fehler
    def check_access_vulnerable(self, user, resource):
        # VERWUNDBAR: Zweite Bedingung nie ausgewertet wenn erste True
        if True or self.has_permission(user, resource):
            return True
        return False

# VERWUNDBAR: Ausnahmebehandlung fängt alles ab
def process_request_vulnerable(request):
    try:
        result = dangerous_operation(request)
    except:  # VERWUNDBAR: Fängt alle Ausnahmen ab
        pass  # VERWUNDBAR: Ignoriert Fehler stillschweigend
    return result  # Könnte undefiniert sein!
// VERWUNDBAR: Java immer-fehlerhafter Kontrollfluss
public class VulnerableControlFlow {

    // VERWUNDBAR: String-Vergleich mit ==
    public boolean checkPassword(String password) {
        // VERWUNDBAR: == vergleicht Referenzen, nicht Inhalt
        if (password == "secret") {  // Normalerweise falsch
            return true;  // Selten erreicht
        }
        return false;
    }

    // VERWUNDBAR: Immer-falsch wegen Null-Check-Reihenfolge
    public boolean validateUser(User user) {
        // VERWUNDBAR: user.getRole() aufgerufen vor Null-Check
        if (user.getRole().equals("admin") && user != null) {
            return true;  // NPE wenn user null ist
        }
        return false;
    }

    // VERWUNDBAR: Unerreichbarer Code
    public int calculate(int value) {
        if (value > 0) {
            return value * 2;
        } else {
            return value * -1;
        }

        // VERWUNDBAR: Nie erreicht
        if (isSpecialValue(value)) {
            return handleSpecial(value);
        }
    }

    // VERWUNDBAR: Boolean-Literal-Vergleich
    public boolean isActive(boolean flag) {
        // VERWUNDBAR: Redundant und fehleranfällig
        if (flag == true) {  // Funktioniert, aber könnte == false sein aus Versehen
            return true;
        }
        return false;
    }

    // VERWUNDBAR: instanceof mit falschem Typ
    public void process(Object obj) {
        // VERWUNDBAR: Immer falsch wenn obj nie String hier sein kann
        if (obj instanceof Integer && obj instanceof String) {  // Unmöglich
            // Nie ausgeführt
            handleSpecial(obj);
        }
    }

    // VERWUNDBAR: Invertierte Bedingung
    public void securityCheck(User user, Resource resource) {
        // VERWUNDBAR: ! negiert gesamten Ausdruck wenn nur erster Teil gemeint
        if (!user.isAuthenticated() && user.hasPermission(resource)) {
            // Beabsichtigt: authentifiziert UND hat Berechtigung
            // Tatsächlich: NICHT authentifiziert UND hat Berechtigung
            grantAccess(resource);  // Falsche Benutzer bekommen Zugriff
        }
    }
}
// VERWUNDBAR: JavaScript immer-fehlerhafter Kontrollfluss
class VulnerableAuth {

    // VERWUNDBAR: Typumwandlungsprobleme
    checkAuth(input) {
        // VERWUNDBAR: '==' führt Typumwandlung durch
        if (input == true) {  // "1" == true ist wahr
            return true;  // Unbeabsichtigte truthy-Werte bestehen
        }

        // Auch verwundbar: '' == false, 0 == false, etc.
        return false;
    }

    // VERWUNDBAR: Zuweisung in Bedingung
    checkAdmin(role) {
        // VERWUNDBAR: Einzelnes = ist Zuweisung
        if (role = 'admin') {  // Immer truthy (nicht-leerer String)
            return true;  // Jeder ist Admin!
        }
        return false;
    }

    // VERWUNDBAR: Array/Object-Truthiness
    checkPermissions(perms) {
        // VERWUNDBAR: Leeres Array ist truthy
        if (perms) {  // [] ist truthy!
            return true;  // Leere Berechtigungen bestehen trotzdem
        }
        return false;
    }

    // VERWUNDBAR: typeof-Vergleichsfehler
    validateInput(value) {
        // VERWUNDBAR: typeof gibt String zurück, Vergleich mit undefined scheitert
        if (typeof value === undefined) {  // Immer falsch
            return false;  // Nie erreicht
        }
        // Sollte sein: typeof value === 'undefined'
        return true;  // Alles besteht
    }

    // VERWUNDBAR: NaN-Vergleich
    validateNumber(num) {
        // VERWUNDBAR: NaN !== NaN
        if (num !== NaN) {  // Immer wahr (auch für NaN!)
            return true;  // NaN besteht Validierung
        }
        return false;
    }

    // VERWUNDBAR: Fließkomma-Vergleich
    checkBalance(amount, required) {
        // VERWUNDBAR: Fließkomma-Präzisionsprobleme
        if (0.1 + 0.2 === 0.3) {  // Falsch! (0.30000000000000004)
            // Dieser Code wird nie ausgeführt
            return amount >= required;
        }
        return false;  // Immer zurückgegeben
    }
}

// VERWUNDBAR: Hoisting-Probleme
function processVulnerable(value) {
    if (value > 0) {
        return process(value);
    }

    // VERWUNDBAR: Variable-Hoisting macht dies undefined, nicht Fehler
    return result;  // Immer undefined

    var result = calculate(value);  // Nie ausgeführt
}

Lösungscode

// SICHER: Korrekter Kontrollfluss in C
#include <stdio.h>
#include <string.h>

// SICHER: Ordnungsgemäßer Vergleich
int check_auth_safe1(const char* password) {
    // SICHER: strcmp für String-Vergleich
    if (strcmp(password, "secret") == 0) {
        return 1;
    }
    return 0;
}

// Alternative: == mit Konstante links (Yoda-Bedingung)
int check_auth_safe2(const char* password) {
    // Compiler-Fehler wenn = statt == verwendet
    if (NULL == password) {
        return 0;
    }
    return strcmp(password, "secret") == 0;
}

// SICHER: Ordnungsgemäße Grenzenprüfung
int validate_index_safe(int index, int max_size) {
    // SICHER: Signed Integer ordnungsgemäß geprüft
    if (index >= 0 && index < max_size) {
        return 1;
    }
    return 0;
}

// SICHER: Ordnungsgemäße Kontrollfluss-Reihenfolge
int process_safe(int value) {
    // Validierung zuerst
    if (!validate_value(value)) {
        log_error("Ungültiger Wert");
        return -1;
    }

    if (value > 0) {
        return 1;
    }

    return 0;
}

// SICHER: Kein Semikolon nach if
void security_check_safe(int user_level) {
    if (user_level >= ADMIN_LEVEL) {
        grant_admin_access();
    }
}

// SICHER: Logische Operatoren
int check_permissions_safe(int has_perm1, int has_perm2) {
    // SICHER: Logisches UND
    if (has_perm1 && has_perm2) {
        return 1;
    }
    return 0;
}
# SICHER: Python korrekter Kontrollfluss
class SafeAuth:

    # SICHER: Ordnungsgemäßer String-Vergleich
    def check_password_safe(self, password):
        # SICHER: == vergleicht Werte
        if password == "secret":
            return True
        return False

    # SICHER: Korrekte return-Platzierung
    def validate_safe(self, data):
        # Validierung läuft zuerst
        if not self.is_valid(data):
            raise ValueError("Ungültige Daten")

        return True  # Nur nach Validierung

    # SICHER: Ordnungsgemäße Negativ-Behandlung
    def process_safe(self, value):
        if value < 0:
            raise ValueError("Negativer Wert nicht erlaubt")

        self.process_value(value)

    # SICHER: Kein veränderliches Standard-Argument
    def add_permission_safe(self, perm, perms=None):
        if perms is None:
            perms = []  # Neue Liste bei jedem Aufruf
        perms.append(perm)
        return perms

    # SICHER: Ordnungsgemäße boolesche Logik
    def check_access_safe(self, user, resource):
        if self.has_permission(user, resource):
            return True
        return False

# SICHER: Spezifische Ausnahmebehandlung
def process_request_safe(request):
    try:
        result = operation(request)
        return result
    except ValidationError as e:
        log_error(f"Validierung fehlgeschlagen: {e}")
        return None
    except OperationError as e:
        log_error(f"Operation fehlgeschlagen: {e}")
        raise
// SICHER: Java korrekter Kontrollfluss
public class SafeControlFlow {

    // SICHER: equals() für String-Vergleich
    public boolean checkPassword(String password) {
        if ("secret".equals(password)) {  // Auch null-sicher
            return true;
        }
        return false;
    }

    // SICHER: Null-Check zuerst
    public boolean validateUser(User user) {
        if (user != null && "admin".equals(user.getRole())) {
            return true;
        }
        return false;
    }

    // SICHER: Alle Pfade erreichbar
    public int calculate(int value) {
        if (isSpecialValue(value)) {
            return handleSpecial(value);
        }

        if (value > 0) {
            return value * 2;
        } else {
            return value * -1;
        }
    }

    // SICHER: Direkte Boolean-Verwendung
    public boolean isActive(boolean flag) {
        return flag;  // Kein Vergleich nötig
    }

    // SICHER: Logische instanceof-Prüfungen
    public void process(Object obj) {
        if (obj instanceof Integer) {
            handleInteger((Integer) obj);
        } else if (obj instanceof String) {
            handleString((String) obj);
        }
    }

    // SICHER: Klare boolesche Logik
    public void securityCheck(User user, Resource resource) {
        // SICHER: Klammern machen Absicht klar
        if (user.isAuthenticated() && user.hasPermission(resource)) {
            grantAccess(resource);
        }
    }
}
// SICHER: JavaScript korrekter Kontrollfluss
class SafeAuth {

    // SICHER: Strikte Gleichheit
    checkAuth(input) {
        if (input === true) {  // Nur tatsächliches true besteht
            return true;
        }
        return false;
    }

    // SICHER: Ordnungsgemäßer Vergleich
    checkAdmin(role) {
        if (role === 'admin') {  // Korrekter Vergleich
            return true;
        }
        return false;
    }

    // SICHER: Array-Länge prüfen
    checkPermissions(perms) {
        if (Array.isArray(perms) && perms.length > 0) {
            return true;
        }
        return false;
    }

    // SICHER: Korrekte typeof-Verwendung
    validateInput(value) {
        if (typeof value === 'undefined') {  // String-Vergleich
            return false;
        }
        return true;
    }

    // SICHER: NaN-Prüfung mit Number.isNaN
    validateNumber(num) {
        if (Number.isNaN(num)) {
            return false;  // Erkennt NaN korrekt
        }
        return true;
    }

    // SICHER: Epsilon-Vergleich für Floats
    checkBalance(amount, required) {
        const epsilon = 0.0001;
        const sum = 0.1 + 0.2;
        if (Math.abs(sum - 0.3) < epsilon) {
            return amount >= required;
        }
        return false;
    }
}

// SICHER: Ordnungsgemäße Variablendeklaration
function processSafe(value) {
    if (value > 0) {
        return process(value);
    }

    // let/const verwenden um Hoisting-Probleme zu vermeiden
    const result = calculate(value);
    return result;
}

// SICHER: Linter-Regeln verwenden
// eslint: eqeqeq, no-cond-assign, no-unreachable

Ausgenutzt in der Praxis

Authentifizierungsumgehung

Invertierte Bedingungen gewähren allen Zugriff.

Autorisierungsfehler

Sicherheitsprüfungen, die nie ausgeführt werden.

Logikbomben

Unerreichbarer Code mit Hintertüren.


Werkzeuge zum Testen/Ausnutzen

  • Statische Analysatoren (Dead-Code-Erkennung).

  • Linter (eslint, pylint, checkstyle).

  • Compiler-Warnungen (-Wall).


CVE-Beispiele

  • Verschiedene CVEs durch Logikfehler in Authentifizierung.

  • Sicherheitsumgehung durch invertierte Bedingungen.


Referenzen

  1. MITRE. "CWE-670: Always-Incorrect Control Flow Implementation." https://cwe.mitre.org/data/definitions/670.html

  2. Best Practices für statische Analyse.