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
| Auswirkung | Details |
|---|---|
| Autorisierung | Bereich: Vollständige Umgehung Sicherheitsprüfungen scheitern oder gelingen immer. |
| Integrität | Bereich: Logikfehler Code verhält sich nie wie beabsichtigt. |
| Verfügbarkeit | Bereich: 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
-
MITRE. "CWE-670: Always-Incorrect Control Flow Implementation." https://cwe.mitre.org/data/definitions/670.html
-
Best Practices für statische Analyse.