Unsachgemäße Erhaltung der Konsistenz zwischen unabhängigen Darstellungen gemeinsamen Zustands
Beschreibung
Unsachgemäße Erhaltung der Konsistenz zwischen unabhängigen Darstellungen gemeinsamen Zustands tritt auf, wenn ein Produkt mehrere verteilte Komponenten unterhält, die jeweils lokale Kopien gemeinsamer Daten halten, aber nicht sicherstellt, dass alle Kopien systemweit synchronisiert bleiben. In verteilten Umgebungen und Systemen mit unabhängigen physischen Komponenten speichert jede typischerweise ihre eigene Kopie kritischer Daten wie Zustand oder Cache. Alle Komponenten müssen die gleiche "Sicht" beibehalten, um koordiniert zu arbeiten. Beispiele umfassen Social-Media-Plattformen, bei denen Benutzer auf verschiedenen Hosts identische Daten benötigen, oder Prozessoren mit Shadow Memory Management Units (MMUs), die übereinstimmende zugängliche Speicherbereiche haben müssen.
Risiko
Inkonsistente Zustandspflege schafft schwerwiegende Sicherheitsauswirkungen. Komponenten können unter falschen Annahmen arbeiten. Zugriffskontrollentscheidungen können inkonsistent werden. Sicherheitstoken können falsch dupliziert oder ungültig gemacht werden. Cache-Kohärenzprobleme können zu Privilege Escalation führen. Transaktionen können in falscher Reihenfolge ausgeführt werden. Benutzer können auf veraltete oder falsche Daten zugreifen. Angreifer können Race Conditions während Synchronisierungsfenstern ausnutzen. Die Systemintegrität kann kompromittiert werden.
Lösung
Minimieren Sie Nicht-Synchronisierungszeiträume und machen Sie den Aktualisierungsprozess so robust wie möglich. Implementieren Sie starke Konsistenzprotokolle für verteilte Komponenten. Verwenden Sie transaktionale Aktualisierungen mit atomaren Operationen. Implementieren Sie ordnungsgemäße Cache-Kohärenzprotokolle. Priorisieren Sie sicherheitskritische Aktualisierungen gegenüber allgemeinem Datenverkehr. Fügen Sie Überprüfung nach Zustandsänderungspropagierung hinzu. Implementieren Sie Konflikterkennungs- und Lösungsmechanismen. Verwenden Sie bei Bedarf verteilte Konsensalgorithmen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Sonstiges | Umfang: Sonstiges Unerwarteter Zustand - Eine oder mehrere Komponenten/Subsysteme könnten unter falschen Annahmen über den tatsächlichen Systemzustand arbeiten. |
Beispielcode und Lösung
Verwundbarer Code
# VERWUNDBAR: Verteilter Cache ohne Synchronisation
import threading
import time
class VulnerableCacheNode:
def __init__(self, node_id):
self.node_id = node_id
self.local_cache = {}
self.version = 0
def get(self, key):
# VERWUNDBAR: Gibt lokale Kopie zurück, ohne Aktualität zu prüfen
return self.local_cache.get(key)
def set(self, key, value):
# VERWUNDBAR: Aktualisiert nur lokale Kopie
# Keine Synchronisation mit anderen Knoten
self.local_cache[key] = value
self.version += 1
def receive_update(self, key, value, version):
# VERWUNDBAR: Keine Reihenfolgegarantie
# Race Condition: Aktualisierungen können in falscher Reihenfolge ankommen
self.local_cache[key] = value
# VERWUNDBAR: Benutzerberechtigungen ohne Konsistenz
class VulnerablePermissionSystem:
def __init__(self):
self.nodes = []
def revoke_permission(self, user_id, permission):
# VERWUNDBAR: Sendet Widerruf, wartet aber nicht auf Bestätigung
for node in self.nodes:
# Fire-and-Forget - keine Garantie, dass alle Knoten aktualisiert wurden
threading.Thread(target=node.remove_permission,
args=(user_id, permission)).start()
# VERWUNDBAR: Kehrt sofort zurück, bevor Propagierung abgeschlossen
# Benutzer hat möglicherweise noch Berechtigung auf einigen Knoten
def check_permission(self, node, user_id, permission):
# VERWUNDBAR: Jeder Knoten hat möglicherweise veraltete Berechtigungsdaten
return node.has_permission(user_id, permission)
# Angriff: Synchronisierungsverzögerung ausnutzen
def exploit_sync_delay():
system = VulnerablePermissionSystem()
# Admin widerruft Berechtigung des Angreifers
system.revoke_permission("attacker", "admin_access")
# Angreifer versucht sofort alle Knoten
# Einige Knoten haben den Widerruf noch nicht erhalten
for node in system.nodes:
if system.check_permission(node, "attacker", "admin_access"):
# ANGRIFF: Knoten mit veralteten Berechtigungen gefunden
perform_admin_action(node)
break
// VERWUNDBAR: Shadow-MMU ohne ordnungsgemäße Synchronisation
module vulnerable_shadow_mmu (
input wire clk,
input wire reset_n,
input wire [31:0] update_addr,
input wire [31:0] update_data,
input wire update_valid,
input wire [31:0] check_addr,
output reg access_allowed
);
// Lokale Kopie der Zugriffsberechtigungen
reg [31:0] permission_table [0:255];
// VERWUNDBAR: Keine Synchronisation mit Master-MMU
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
integer i;
for (i = 0; i < 256; i = i + 1) begin
permission_table[i] <= 32'h0;
end
end
else if (update_valid) begin
// VERWUNDBAR: Aktualisierungen kommen ohne Reihenfolge
// Race Condition mit Zugriffsüberprüfungen
permission_table[update_addr[7:0]] <= update_data;
end
end
// VERWUNDBAR: Prüfung gegen möglicherweise veraltete Daten
always @(*) begin
access_allowed = permission_table[check_addr[7:0]][0];
end
// Kein Mechanismus, um:
// - Zu überprüfen, ob Aktualisierung aus legitimer Quelle kam
// - Sicherzustellen, dass alle Shadow-MMUs gleiche Aktualisierung erhalten haben
// - Inkonsistenz zwischen Master und Shadow zu erkennen
endmodule
// VERWUNDBAR: Verteilter Session-Speicher ohne Konsistenz
public class VulnerableSessionStore {
private Map<String, Session> localSessions = new ConcurrentHashMap<>();
private List<VulnerableSessionStore> peers;
public void invalidateSession(String sessionId) {
// Lokal entfernen
localSessions.remove(sessionId);
// VERWUNDBAR: Asynchroner Broadcast an Peers ohne Bestätigung
for (VulnerableSessionStore peer : peers) {
CompletableFuture.runAsync(() -> {
peer.receiveInvalidation(sessionId);
});
}
// VERWUNDBAR: Kehrt zurück, bevor alle Peers Invalidierung bestätigt haben
}
public boolean isSessionValid(String sessionId) {
// VERWUNDBAR: Prüft nur lokale Kopie
// Kann true für invalidierte Session zurückgeben, wenn Aktualisierung noch nicht angekommen
return localSessions.containsKey(sessionId);
}
public void receiveInvalidation(String sessionId) {
// VERWUNDBAR: Keine Reihenfolgegarantie
// Netzwerkverzögerung könnte veraltete Session gültig lassen
localSessions.remove(sessionId);
}
}
Sichere Lösung
# SICHER: Verteilter Cache mit stärker Konsistenz
import threading
import time
from collections import defaultdict
class SecureCacheNode:
def __init__(self, node_id, coordinator):
self.node_id = node_id
self.local_cache = {}
self.versions = {}
self.coordinator = coordinator
self.lock = threading.Lock()
def get(self, key):
with self.lock:
# SICHER: Version mit Koordinator prüfen, bevor zurückgegeben wird
local_version = self.versions.get(key, 0)
current_version = self.coordinator.get_current_version(key)
if local_version < current_version:
# Veraltete Daten - frische Kopie holen
self.local_cache[key], self.versions[key] = \
self.coordinator.get_authoritative(key)
return self.local_cache.get(key)
def set(self, key, value):
# SICHER: Aktualisierung über zentrale Autorität koordinieren
success = self.coordinator.request_update(key, value, self.node_id)
if success:
with self.lock:
self.local_cache[key] = value
self.versions[key] = self.coordinator.get_current_version(key)
return success
# SICHER: Berechtigungssystem mit stärker Konsistenz
class SecurePermissionSystem:
def __init__(self):
self.nodes = []
self.pending_revocations = {}
self.lock = threading.Lock()
def revoke_permission(self, user_id, permission):
revocation_id = generate_unique_id()
with self.lock:
# Ausstehenden Widerruf verfolgen
self.pending_revocations[revocation_id] = {
'user_id': user_id,
'permission': permission,
'confirmed_nodes': set(),
'total_nodes': len(self.nodes)
}
# SICHER: Auf Bestätigung aller Knoten warten
confirmation_events = []
for node in self.nodes:
event = threading.Event()
confirmation_events.append(event)
node.remove_permission_with_ack(
user_id, permission, revocation_id, event)
# SICHER: Blockieren, bis alle Knoten bestätigt haben
all_confirmed = all(event.wait(timeout=30) for event in confirmation_events)
if not all_confirmed:
# Teilweise Propagierung behandeln - ausfallsicher
self.emergency_revoke(user_id, permission)
raise SynchronizationError("Widerruf könnte nicht an alle Knoten propagiert werden")
with self.lock:
del self.pending_revocations[revocation_id]
def check_permission(self, node, user_id, permission):
with self.lock:
# SICHER: Erst auf ausstehende Widerrufe prüfen
for revocation in self.pending_revocations.values():
if (revocation['user_id'] == user_id and
revocation['permission'] == permission):
# Widerruf in Bearbeitung - Zugriff verweigern
return False
return node.has_permission(user_id, permission)
def emergency_revoke(self, user_id, permission):
# Allen Zugriff blockieren, während Synchronisation läuft
for node in self.nodes:
node.block_user(user_id)
// SICHER: Shadow-MMU mit Synchronisierungsüberprüfung
module secure_shadow_mmu (
input wire clk,
input wire reset_n,
input wire [31:0] update_addr,
input wire [31:0] update_data,
input wire [31:0] update_sequence, // Sequenznummer für Reihenfolge
input wire update_valid,
input wire [31:0] master_checksum, // Prüfsumme von Master-MMU
input wire [31:0] check_addr,
output reg access_allowed,
output reg sync_error
);
reg [31:0] permission_table [0:255];
reg [31:0] expected_sequence;
reg [31:0] local_checksum;
// SICHER: Synchronisierungszustand verfolgen
reg synchronized;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
integer i;
for (i = 0; i < 256; i = i + 1) begin
permission_table[i] <= 32'h0;
end
expected_sequence <= 32'h0;
local_checksum <= 32'h0;
synchronized <= 1'b1;
sync_error <= 1'b0;
end
else if (update_valid) begin
// SICHER: Aktualisierungssequenz überprüfen
if (update_sequence == expected_sequence) begin
permission_table[update_addr[7:0]] <= update_data;
expected_sequence <= expected_sequence + 1;
// Prüfsumme aktualisieren
local_checksum <= local_checksum ^ update_data;
end
else if (update_sequence > expected_sequence) begin
// SICHER: Aktualisierung verpasst - als nicht synchronisiert markieren
synchronized <= 1'b0;
sync_error <= 1'b1;
end
// Doppelte/alte Aktualisierungen ignorieren
end
end
// SICHER: Periodische Konsistenzprüfung
always @(posedge clk) begin
if (synchronized && (local_checksum != master_checksum)) begin
// Prüfsummen-Diskrepanz - nicht synchronisiert
synchronized <= 1'b0;
sync_error <= 1'b1;
end
end
// SICHER: Zugriff verweigern, wenn nicht synchronisiert (ausfallsicher)
always @(*) begin
if (!synchronized) begin
access_allowed = 1'b0; // Ausfallsicher bei Nicht-Synchronisation
end
else begin
access_allowed = permission_table[check_addr[7:0]][0];
end
end
endmodule
// SICHER: Verteilter Session-Speicher mit stärker Konsistenz
public class SecureSessionStore {
private Map<String, Session> localSessions = new ConcurrentHashMap<>();
private Map<String, Long> sessionVersions = new ConcurrentHashMap<>();
private List<SecureSessionStore> peers;
private final Object syncLock = new Object();
public void invalidateSession(String sessionId) throws SynchronizationException {
long invalidationVersion = System.nanoTime();
// SICHER: Zwei-Phasen-Commit für Session-Invalidierung
List<CompletableFuture<Boolean>> preparePhase = new ArrayList<>();
// Phase 1: Vorbereitung
for (SecureSessionStore peer : peers) {
preparePhase.add(CompletableFuture.supplyAsync(() ->
peer.prepareInvalidation(sessionId, invalidationVersion)));
}
// Auf Bestätigung aller Peers warten
boolean allPrepared = preparePhase.stream()
.allMatch(future -> {
try {
return future.get(10, TimeUnit.SECONDS);
} catch (Exception e) {
return false;
}
});
if (!allPrepared) {
// Abbruch - Rollback
for (SecureSessionStore peer : peers) {
peer.abortInvalidation(sessionId);
}
throw new SynchronizationException("Vorbereitung aller Knoten fehlgeschlagen");
}
// Phase 2: Commit
List<CompletableFuture<Void>> commitPhase = new ArrayList<>();
for (SecureSessionStore peer : peers) {
commitPhase.add(CompletableFuture.runAsync(() ->
peer.commitInvalidation(sessionId, invalidationVersion)));
}
// Lokal entfernen nach allen Commits
CompletableFuture.allOf(commitPhase.toArray(new CompletableFuture[0])).join();
localSessions.remove(sessionId);
sessionVersions.put(sessionId, invalidationVersion);
}
public boolean isSessionValid(String sessionId) {
synchronized (syncLock) {
// SICHER: Prüfen, ob Invalidierung in Bearbeitung
if (pendingInvalidations.contains(sessionId)) {
return false;
}
// SICHER: Lokale Version mit Quorum abgleichen
Long localVersion = sessionVersions.get(sessionId);
if (!verifyVersionWithQuorum(sessionId, localVersion)) {
// Versionsdiskrepanz - von Peers aktualisieren
refreshSession(sessionId);
}
return localSessions.containsKey(sessionId);
}
}
private boolean verifyVersionWithQuorum(String sessionId, Long localVersion) {
int matches = 0;
int quorum = (peers.size() / 2) + 1;
for (SecureSessionStore peer : peers) {
if (Objects.equals(peer.getVersion(sessionId), localVersion)) {
matches++;
if (matches >= quorum) {
return true;
}
}
}
return false;
}
}
CVE-Beispiele
Schwachstellen in der Zustandskonsistenz wurden in verschiedenen verteilten Systemen gefunden, darunter Cloud-Plattformen, CDN-Netzwerke und Mehrprozessorsysteme, bei denen Angreifer Synchronisierungsfenster ausnutzten, um Sicherheitskontrollen zu umgehen.
Verwandte CWEs
- CWE-664: Unsachgemäße Kontrolle einer Ressource während ihrer Lebensdauer (übergeordnet)
- CWE-1249: Admin-Tool auf Anwendungsebene mit inkonsistenter Sicht (untergeordnet)
- CWE-1251: Gespiegelte Regionen mit unterschiedlichen Werten (untergeordnet)
- CWE-362: Nebenläufige Ausführung mit gemeinsamer Ressource bei unsachgemäßer Synchronisation (verwandt)
Referenzen
- MITRE Corporation. "CWE-1250: Improper Preservation of Consistency Between Independent Representations of Shared State." https://cwe.mitre.org/data/definitions/1250.html
- Lamport, Leslie. "The Part-Time Parliament" (Paxos Consensus)
- Ongaro, Diego. "In Search of an Understandable Consensus Algorithm" (Raft)