Erwartete Verhaltensverletzung
Beschreibung
Erwartete Verhaltensverletzung ist eine Schwachstelle, bei der eine Funktion, API oder Feature nicht gemäß ihrer Spezifikation oder dokumentiertem Verhalten funktioniert. Softwareentwickler verlassen sich auf dokumentiertes Verhalten, um sichere und zuverlässige Systeme zu bauen. Wenn sich eine Komponente anders verhält als spezifiziert, können Aufrufer falsche Annahmen machen, die zu Sicherheitsschwachstellen, Datenkorrumpierung oder Systemausfällen führen. Dies betrifft alle Schichten von Hardware-Registern bis zu High-Level-APIs und kann besonders gefährlich in sicherheitskritischem Code sein, der von spezifischen Verhaltensgarantien abhängt.
Risiko
Erwartete Verhaltensverletzungen untergraben das fundamentale Vertrauensmodell der Softwareentwicklung. Sicherheitscode, der von spezifischen Rückgabewerten, Fehlerbehandlung oder Zustandsänderungen abhängt, kann stillschweigend versagen, wenn Komponenten sich nicht wie dokumentiert verhalten. Drittanbieter-Bibliotheken mit undokumentierten Verhaltensvariationen können Schwachstellen in ansonsten sicheren Code einführen. Hardware-Verhaltensverletzungen können Privilegieneskalation ermöglichen, wenn Sicherheitskontrollen anders funktionieren als dokumentiert. Die stille Natur dieser Fehler macht sie schwer durch Tests zu erkennen und besonders gefährlich in Produktionsumgebungen, wo auf Annahmen für Sicherheit vertraut wird.
Lösung
Testen Sie gründlich, dass Komponenten sich gemäß ihren Spezifikationen verhalten. Verwenden Sie defensive Programmierung, um Annahmen zu verifizieren, anstatt allein der Dokumentation zu vertrauen. Implementieren Sie Laufzeit-Assertions, um Verhaltensverletzungen früh zu erkennen. Wenn Sie Drittanbieter-Komponenten verwenden, erstellen Sie Integrationstests, die erwartetes Verhalten verifizieren. Dokumentieren Sie bekannte Verhaltensabweichungen und ihre Sicherheitsimplikationen. Für sicherheitskritischen Code verifizieren Sie Verhalten durch mehrere unabhängige Mechanismen. Erwägen Sie formale Verifikation für Hardware-Komponenten, bei denen Verhalten garantiert sein muss. Melden Sie Verhaltensverletzungen an Anbieter und verfolgen Sie sie als potenzielle Sicherheitsprobleme.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Sonstiges | Umfang: Sonstiges Qualitätsverschlechterung - Software verhält sich inkorrekt, wenn Komponenten Spezifikationen nicht erfüllen, was zu unzuverlässigem Betrieb führt. |
| Sonstiges | Umfang: Sonstiges Variiert je nach Kontext - Sicherheitsimplikationen hängen von der spezifischen Verhaltensverletzung ab und wie der Aufrufer das unerwartete Verhalten behandelt. |
Beispielcode
Anfälliger Code
// Anfällig: Verlasst sich auf dokumentiertes strncpy-Verhalten
#include <string.h>
#include <stdio.h>
void vulnerable_string_copy(const char *input) {
char buffer[64];
// Anfällig: strncpy-Verhalten unterscheidet sich zwischen Implementierungen
// Manche garantieren Null-Terminierung, andere nicht
// Linux libc: terminiert NICHT mit Null wenn source >= n
// Manche eingebettete Bibliotheken: terminieren immer mit Null
strncpy(buffer, input, sizeof(buffer));
// Anfällig: Nimmt an, buffer ist null-terminiert
// Wenn input >= 64 Zeichen, ist buffer auf Linux NICHT null-terminiert
printf("Kopiert: %s\n", buffer); // Kann über buffer hinaus lesen
}
// Anfällig: Verlasst sich auf Timeout-Verhalten
int vulnerable_network_read(int socket, char *buffer, size_t len) {
// Anfällig: Nimmt an, SO_RCVTIMEO funktioniert wie dokumentiert
// Manche Implementierungen ignorieren es für bestimmte Socket-Typen
// Manche geben andere Fehlercodes als dokumentiert zurück
struct timeval tv = {.tv_sec = 5, .tv_usec = 0};
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// Anfällig: Kann unbegrenzt blockieren wenn Timeout nicht eingehalten wird
ssize_t bytes = read(socket, buffer, len);
return bytes;
}
// Anfällig: Hardware-Register mit unerwartetem Verhalten
// Beispiel basierend auf tatsächlicher RISC-V-Schwachstelle
module vulnerable_csr_regfile (
input wire clk,
input wire rst_n,
input wire [11:0] csr_addr,
input wire [63:0] csr_wdata,
input wire csr_we,
output reg [63:0] csr_rdata
);
reg [63:0] mie; // Machine Interrupt Enable
reg [63:0] utval; // User Trap Value
// Anfällig: utval beeinflusst mie-Berechnung inkorrekt
// Spezifikation sagt, utval soll nur Trap-Informationen speichern
// Aber diese Implementierung verwendet es in mie_d-Zuweisung
wire [63:0] mie_d = csr_wdata | utval; // ANFÄLLIG: utval einbezogen
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mie <= 64'b0;
end else if (csr_we && csr_addr == 12'h304) begin
mie <= mie_d; // Benutzer kann mie über utval korrumpieren
end
end
// Angriff: Benutzer schreibt in utval, löst dann mie-Schreiben aus
// Resultiert in Privilegieneskalation oder DoS
endmodule
# Anfällig: Verlasst sich auf dokumentiertes Bibliotheks-Verhalten
import json
import yaml
def vulnerable_parse_config(config_string, format_type):
# Anfällig: Nimmt an, Parsing-Verhalten entspricht Dokumentation
if format_type == 'json':
# Anfällig: Manche JSON-Parser behandeln Duplikate unterschiedlich
# Dokumentation sagt möglicherweise "letzter Wert gewinnt"
# Aber Implementierung könnte ersten Wert verwenden
# {"admin": false, "admin": true} - welcher gewinnt?
config = json.loads(config_string)
elif format_type == 'yaml':
# Anfällig: yaml.load()-Verhalten änderte sich zwischen Versionen
# Alt: gefährlich, erlaubt Code-Ausführung
# Neu: kann bestimmte Eingaben ablehnen
config = yaml.load(config_string) # Unsicher!
# Anfällig: Nimmt an, 'get' gibt None für fehlende Schlüssel zurück
# Manche dict-ähnliche Objekte werfen stattdessen KeyError
is_admin = config.get('is_admin', False)
return config, is_admin
// Anfällig: Verlasst sich auf substring-Verhalten
public class VulnerableStringHandler {
public String extractToken(String input) {
// Anfällig: substring-Verhalten änderte sich in Java 7
// Java 6: substring teilt Backing-Array (speichereffizient, Sicherheitsproblem)
// Java 7+: substring erstellt neues Array (sicher aber anders)
String fullString = readLargeSecret(); // Enthält sensible Daten
// Anfällig auf Java 6: token teilt Speicher mit fullString
// Sensible Daten bleiben über token's Backing-Array zugänglich
String token = fullString.substring(0, 32);
fullString = null; // Versucht sensible Daten zu löschen
// Aber auf Java 6 sind Daten noch über token zugänglich!
return token;
}
// Anfällig: Verlasst sich auf hashCode-Verhalten
public boolean checkCache(String key) {
// Anfällig: String hashCode-Implementierung kann variieren
// Angriff: Strings mit gleichem hashCode erstellen um DoS zu verursachen
// Javas hashCode ist dokumentiert aber konsistente Kollisionen
// sind möglich, was HashMap-Degradation verursacht
return cache.containsKey(key);
}
}
Korrigierter Code
// Korrigiert: Defensive String-Behandlung
#include <string.h>
#include <stdio.h>
void secure_string_copy(const char *input) {
char buffer[64];
// Korrigiert: Null-Terminierung explizit behandeln
size_t input_len = strlen(input);
size_t copy_len = input_len < sizeof(buffer) - 1 ?
input_len : sizeof(buffer) - 1;
memcpy(buffer, input, copy_len);
buffer[copy_len] = '\0'; // Korrigiert: Immer null-terminieren
printf("Kopiert: %s\n", buffer);
}
// Alternative: strlcpy verwenden wo verfügbar
void secure_string_copy_strlcpy(const char *input) {
char buffer[64];
// Korrigiert: strlcpy terminiert immer mit Null
size_t result = strlcpy(buffer, input, sizeof(buffer));
if (result >= sizeof(buffer)) {
// Korrigiert: Abschneidung erkennen
log_warning("Eingabe abgeschnitten von %zu auf %zu", result, sizeof(buffer) - 1);
}
printf("Kopiert: %s\n", buffer);
}
// Korrigiert: Defensive Timeout-Behandlung
int secure_network_read(int socket, char *buffer, size_t len) {
struct pollfd pfd = {
.fd = socket,
.events = POLLIN
};
// Korrigiert: poll für zuverlässiges Timeout verwenden
int poll_result = poll(&pfd, 1, 5000); // 5 Sekunden Timeout
if (poll_result == 0) {
return -1; // Timeout
} else if (poll_result < 0) {
return -2; // Fehler
}
// Korrigiert: Jetzt sicher zu lesen - Daten sind verfügbar
ssize_t bytes = read(socket, buffer, len);
return bytes;
}
// Korrigiert: Hardware-Register mit korrektem Verhalten
module secure_csr_regfile (
input wire clk,
input wire rst_n,
input wire [11:0] csr_addr,
input wire [63:0] csr_wdata,
input wire csr_we,
output reg [63:0] csr_rdata
);
reg [63:0] mie; // Machine Interrupt Enable
reg [63:0] utval; // User Trap Value
// Korrigiert: mie-Berechnung beinhaltet nicht utval
// utval wird nur für seinen spezifizierten Zweck verwendet
wire [63:0] mie_d = csr_wdata; // KORRIGIERT: Verwendet nur Schreibdaten
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
mie <= 64'b0;
utval <= 64'b0;
end else if (csr_we) begin
case (csr_addr)
12'h304: mie <= mie_d; // mie-Register
12'h043: utval <= csr_wdata; // utval-Register (separat)
endcase
end
end
// Korrigiert: Benutzer kann mie nicht über utval korrumpieren
// Jedes Register arbeitet unabhängig gemäß Spezifikation
endmodule
# Korrigiert: Defensives Parsing mit Verhaltens-Verifizierung
import json
import yaml
from typing import Any, Dict, Tuple
def secure_parse_config(config_string: str, format_type: str) -> Tuple[Dict, bool]:
config: Dict[str, Any] = {}
if format_type == 'json':
# Korrigiert: Strikten Parsing-Modus verwenden
config = json.loads(config_string, strict=True)
# Korrigiert: Keine doppelten Schlüssel verifizieren
# Benutzerdefinierter Decoder der Duplikate erkennt
def detect_duplicates(pairs):
seen = set()
result = {}
for key, value in pairs:
if key in seen:
raise ValueError(f"Doppelter Schlüssel in JSON: {key}")
seen.add(key)
result[key] = value
return result
config = json.loads(config_string, object_pairs_hook=detect_duplicates)
elif format_type == 'yaml':
# Korrigiert: Sicheren Loader verwenden
config = yaml.safe_load(config_string)
# Korrigiert: Verifizieren, dass es ein Dict ist
if not isinstance(config, dict):
raise ValueError("Config muss ein Dictionary sein")
# Korrigiert: Explizite Default-Behandlung
if 'is_admin' not in config:
is_admin = False
else:
is_admin = bool(config['is_admin'])
return config, is_admin
# Korrigiert: Verhalten beim Start verifizieren
def verify_json_behavior():
"""JSON-Parsing-Verhalten entspricht Erwartungen verifizieren."""
test_input = '{"key": 1, "key": 2}'
result = json.loads(test_input)
# Tatsächliches Verhalten dokumentieren
if result['key'] == 2:
print("JSON-Parser verwendet letzter-Wert-gewinnt für Duplikate")
else:
print("JSON-Parser verwendet erster-Wert-gewinnt für Duplikate")
# Unser sicherer Parser sollte dies ablehnen
try:
secure_parse_config(test_input, 'json')
raise AssertionError("Erwartete Duplikat-Schlüssel-Ablehnung")
except ValueError:
print("Duplikat-Schlüssel-Ablehnung funktioniert korrekt")
// Korrigiert: Defensive String-Behandlung
import java.security.SecureRandom;
import java.util.Arrays;
public class SecureStringHandler {
public String extractToken(String input) {
byte[] fullData = readLargeSecretAsBytes();
// Korrigiert: Unabhängige Kopie erstellen
byte[] tokenBytes = Arrays.copyOfRange(fullData, 0, 32);
// Korrigiert: Originaldaten löschen
Arrays.fill(fullData, (byte) 0);
// Korrigiert: Neuen String aus unabhängigem Byte-Array erstellen
String token = new String(tokenBytes, StandardCharsets.UTF_8);
// Korrigiert: Token-Bytes löschen
Arrays.fill(tokenBytes, (byte) 0);
return token;
}
// Korrigiert: Kollisionsresistente Schlüssel-Behandlung verwenden
public boolean checkCache(String key) {
// Korrigiert: TreeMap für garantiertes O(log n) unabhängig von hashCode verwenden
// Oder Schlüssel-Hashing verwenden das kollisionsresistent ist
String hashedKey = computeSecureHash(key);
return cache.containsKey(hashedKey);
}
private String computeSecureHash(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 nicht verfügbar", e);
}
}
}
CVE-Beispiele
- CVE-2003-0187 - Timeout-Funktionalität verhielt sich inkonsistent, wodurch Sicherheitsprüfungen umgangen wurden.
- CVE-2003-0465 - strncpy-Verhaltensunterschiede zwischen Linux-Kernel und libc führten zu Sicherheitsproblemen.
- CVE-2005-3265 - Drittanbieter-Bibliothek fehlte erwarteter Buffer Overflow-Schutz, was Schwachstelle im aufrufenden Code verursachte.
Referenzen
- MITRE Corporation. "CWE-440: Expected Behavior Violation." https://cwe.mitre.org/data/definitions/440.html
- RISC-V Foundation. "RISC-V Privileged Architecture Specification."