Unsachgemäße Synchronisierung
Beschreibung
Unsachgemäße Synchronisierung tritt auf, wenn der gleichzeitige Zugriff auf gemeinsam genutzte Ressourcen nicht ordnungsgemäß kontrolliert wird, was zu Race Conditions, Datenkorruption oder Sicherheitslücken führt. Dies umfasst fehlende Sperren, falschen Sperrbereich, Probleme mit der Sperrreihenfolge und Time-of-Check-to-Time-of-Use (TOCTOU)-Bedingungen. Im Sicherheitskontext kann unsachgemäße Synchronisierung es Angreifern ermöglichen, den Programmzustand zwischen Prüfungen und Operationen zu manipulieren.
Risiko
Race Conditions ermöglichen Privilegieneskalation. TOCTOU-Angriffe umgehen Zugriffskontrollen. Datenkorruption durch gleichzeitige Schreibvorgänge. Deadlocks verursachen Denial of Service. Double-Spending in Finanzanwendungen. Authentifizierungsumgehung durch Timing-Angriffe. Session-Fixierung durch Race Conditions.
Lösung
Verwenden Sie geeignete Synchronisierungsprimitive. Implementieren Sie atomare Operationen für Check-and-Act-Sequenzen. Nutzen Sie Transaktionen für Datenbankoperationen. Wenden Sie Sperrreihenfolge an, um Deadlocks zu verhindern. Verwenden Sie thread-sichere Datenstrukturen. Implementieren Sie ordnungsgemäßes Session-Locking. Testen Sie auf Race Conditions mit Fuzzing.
Häufige Konsequenzen
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Datenkorruption Gleichzeitiger Zugriff beschädigt gemeinsamen Zustand. |
| Autorisierung | Bereich: Umgehung Race Conditions umgehen Sicherheitsprüfungen. |
| Verfügbarkeit | Bereich: Deadlock/DoS Unsachgemäßes Sperren verursacht Systemhänger. |
Beispielcode + Lösungscode
Verwundbarer Code
// VERWUNDBAR: Race Condition bei Authentifizierung
public class VulnerableAuthService {
private Map<String, Session> sessions = new HashMap<>(); // Nicht thread-sicher!
public Session login(String username, String password) {
// VERWUNDBAR: TOCTOU Race Condition
if (validateCredentials(username, password)) {
// Lücke zwischen Prüfung und Session-Erstellung
Session session = new Session(username);
sessions.put(session.getId(), session); // Thread-unsicheres Schreiben
return session;
}
return null;
}
public boolean isLoggedIn(String sessionId) {
// VERWUNDBAR: Prüfung ohne Synchronisierung
return sessions.containsKey(sessionId);
}
}
// VERWUNDBAR: Race Condition bei Kontostand-Prüfung
public class VulnerableBankAccount {
private double balance; // Nicht atomar
public boolean withdraw(double amount) {
// VERWUNDBAR: TOCTOU - Prüfung und Aktion nicht atomar
if (balance >= amount) {
// Ein anderer Thread könnte zwischen Prüfung und Update abheben
balance -= amount; // Nicht-atomare Operation
return true;
}
return false;
}
// Doppelabhebungsangriff möglich:
// Thread A: prüft Kontostand (100), sieht ausreichend Guthaben
// Thread B: prüft Kontostand (100), sieht ausreichend Guthaben
// Thread A: hebt 100 ab, Kontostand = 0
// Thread B: hebt 100 ab, Kontostand = -100 (Überziehung!)
}
// VERWUNDBAR: Dateibasiertes TOCTOU
public class VulnerableFileAccess {
public void readFile(String filename) {
File file = new File(filename);
// VERWUNDBAR: TOCTOU Race Condition
if (file.exists() && file.canRead()) {
// Lücke: Angreifer könnte Datei hier durch Symlink ersetzen
// zwischen Prüfung und Verwendung
return Files.readAllBytes(file.toPath());
}
}
}
# VERWUNDBAR: Python Race Conditions
import threading
class VulnerableCounter:
def __init__(self):
self.count = 0 # Nicht thread-sicher
def increment(self):
# VERWUNDBAR: Read-Modify-Write nicht atomar
current = self.count
# Kontextwechsel könnte hier passieren
self.count = current + 1
# VERWUNDBAR: Authentifizierungs-Race Condition
class VulnerableAuth:
def __init__(self):
self.sessions = {} # Nicht thread-sichere Dict-Operationen
def login(self, user_id, password):
# VERWUNDBAR: Check then act
if self.validate(user_id, password):
# Race: anderer Thread könnte zwischen Prüfung und Erstellung invalidieren
session = create_session(user_id)
self.sessions[session.id] = session
return session
def check_session(self, session_id):
# VERWUNDBAR: Keine Synchronisierung
if session_id in self.sessions:
# Race: Session könnte zwischen Prüfung und Verwendung entfernt werden
return self.sessions[session_id]
# VERWUNDBAR: TOCTOU Dateizugriff
import os
def process_file_vulnerable(filepath):
# VERWUNDBAR: Check then use
if os.path.exists(filepath) and os.access(filepath, os.R_OK):
# Angreifer könnte Datei hier durch Symlink ersetzen
with open(filepath, 'r') as f:
return f.read()
# VERWUNDBAR: Race Condition bei Rate-Limiting
class VulnerableRateLimiter:
def __init__(self):
self.counts = {}
def is_allowed(self, user_id):
# VERWUNDBAR: Nicht atomar
if user_id not in self.counts:
self.counts[user_id] = 0
current = self.counts[user_id]
if current < 100:
self.counts[user_id] = current + 1 # Race Condition
return True
return False
// VERWUNDBAR: Node.js Race Conditions
class VulnerableService {
constructor() {
this.data = {};
this.sessions = new Map();
}
// VERWUNDBAR: Async Race Condition
async withdraw(accountId, amount) {
const account = await this.getAccount(accountId);
// VERWUNDBAR: Check then act mit async Lücke
if (account.balance >= amount) {
// Eine andere Anfrage könnte zwischen Prüfung und Update verarbeiten
account.balance -= amount;
await this.saveAccount(account);
return true;
}
return false;
}
// VERWUNDBAR: Session Race Condition
async createSession(userId) {
const existingSession = await this.findSession(userId);
// VERWUNDBAR: Race Condition
if (!existingSession) {
// Zwei Anfragen könnten beide keine Session sehen und Duplikate erstellen
const session = { id: generateId(), userId };
this.sessions.set(session.id, session);
return session;
}
return existingSession;
}
// VERWUNDBAR: Zähler Race
async incrementCounter(key) {
const value = await this.redis.get(key) || 0;
// VERWUNDBAR: Nicht atomar
await this.redis.set(key, parseInt(value) + 1);
}
}
// VERWUNDBAR: Dateiprüfung dann Verwendung
const fs = require('fs').promises;
async function readFileVulnerable(filepath) {
try {
await fs.access(filepath, fs.constants.R_OK);
// Lücke: Datei könnte sich zwischen Prüfung und Lesen ändern
return await fs.readFile(filepath);
} catch (e) {
return null;
}
}
// VERWUNDBAR: C Race Conditions
#include <pthread.h>
// VERWUNDBAR: Ungeschützter gemeinsamer Zähler
int global_counter = 0;
void* increment_vulnerable(void* arg) {
for (int i = 0; i < 1000000; i++) {
// VERWUNDBAR: Read-Modify-Write nicht atomar
global_counter++; // Nicht thread-sicher
}
return NULL;
}
// VERWUNDBAR: TOCTOU Dateizugriff
int access_file_vulnerable(const char* filename) {
struct stat st;
// VERWUNDBAR: Check then use
if (stat(filename, &st) == 0) {
if (st.st_uid == getuid()) {
// Angreifer könnte Datei zwischen Prüfung und Öffnen austauschen
int fd = open(filename, O_RDONLY);
// ...
}
}
}
// VERWUNDBAR: Double-Free Race Condition
void* shared_ptr = NULL;
pthread_mutex_t mutex; // Aber nicht korrekt verwendet
void free_shared_vulnerable() {
// VERWUNDBAR: Check then act ohne gehaltene Sperre
if (shared_ptr != NULL) {
// Ein anderer Thread könnte zwischen Prüfung und Free freigeben
free(shared_ptr);
shared_ptr = NULL;
}
}
Lösungscode
// SICHER: Ordnungsgemäße Synchronisierung
public class SafeAuthService {
private final ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();
public Session login(String username, String password) {
if (validateCredentials(username, password)) {
Session session = new Session(username);
// SICHER: Atomare put-Operation
sessions.put(session.getId(), session);
return session;
}
return null;
}
public boolean isLoggedIn(String sessionId) {
// SICHER: Thread-sichere Prüfung
return sessions.containsKey(sessionId);
}
}
// SICHER: Atomare Kontostand-Operationen
public class SafeBankAccount {
private final AtomicReference<BigDecimal> balance;
private final Object withdrawLock = new Object();
public SafeBankAccount(BigDecimal initialBalance) {
this.balance = new AtomicReference<>(initialBalance);
}
// SICHER: Compare-and-Swap verwenden
public boolean withdrawCAS(BigDecimal amount) {
while (true) {
BigDecimal current = balance.get();
if (current.compareTo(amount) < 0) {
return false; // Unzureichendes Guthaben
}
BigDecimal newBalance = current.subtract(amount);
if (balance.compareAndSet(current, newBalance)) {
return true; // Erfolgreich abgehoben
}
// Wiederholen falls anderer Thread modifiziert hat
}
}
// Oder mit synchronized
public synchronized boolean withdrawSync(BigDecimal amount) {
if (balance.get().compareTo(amount) >= 0) {
balance.set(balance.get().subtract(amount));
return true;
}
return false;
}
}
// SICHER: TOCTOU mit atomaren Operationen vermeiden
public class SafeFileAccess {
public byte[] readFile(Path path) {
try {
// SICHER: Einzelne atomare Operation, kein TOCTOU
return Files.readAllBytes(path);
} catch (NoSuchFileException | AccessDeniedException e) {
// Zugriffsprobleme behandeln
return null;
}
}
// SICHER: Dateisperren für exklusiven Zugriff verwenden
public void writeFileExclusive(Path path, byte[] data) throws IOException {
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
// SICHER: Exklusive Sperre
try (FileLock lock = channel.lock()) {
channel.write(ByteBuffer.wrap(data));
}
}
}
}
# SICHER: Python mit ordnungsgemäßer Synchronisierung
import threading
from threading import Lock, RLock
import fcntl
class SafeCounter:
def __init__(self):
self.count = 0
self.lock = Lock()
def increment(self):
# SICHER: Atomare Operation mit Sperre
with self.lock:
self.count += 1
# SICHER: Thread-sichere Authentifizierung
class SafeAuth:
def __init__(self):
self.sessions = {}
self.lock = RLock()
def login(self, user_id, password):
if self.validate(user_id, password):
# SICHER: Synchronisierte Session-Erstellung
with self.lock:
session = create_session(user_id)
self.sessions[session.id] = session
return session
return None
def check_session(self, session_id):
# SICHER: Synchronisierter Zugriff
with self.lock:
return self.sessions.get(session_id)
# SICHER: TOCTOU mit atomaren Operationen vermeiden
def process_file_safe(filepath):
try:
# SICHER: Einzelne atomare Operation
with open(filepath, 'r') as f:
return f.read()
except (FileNotFoundError, PermissionError):
return None
# SICHER: Dateisperren für exklusiven Zugriff
def write_file_safe(filepath, content):
with open(filepath, 'w') as f:
# SICHER: Exklusive Dateisperre
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
f.write(content)
finally:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
# SICHER: Atomares Rate-Limiting mit Redis
import redis
class SafeRateLimiter:
def __init__(self):
self.redis = redis.Redis()
def is_allowed(self, user_id, limit=100, window=60):
key = f"ratelimit:{user_id}"
# SICHER: Atomares Inkrement mit Lua-Skript
script = """
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return current
"""
current = self.redis.eval(script, 1, key, window)
return current <= limit
// SICHER: Node.js mit ordnungsgemäßer Synchronisierung
const { Mutex } = require('async-mutex');
class SafeService {
constructor() {
this.mutex = new Mutex();
this.sessions = new Map();
}
// SICHER: Mutex für atomare Operationen verwenden
async withdraw(accountId, amount) {
const release = await this.mutex.acquire();
try {
const account = await this.getAccount(accountId);
if (account.balance >= amount) {
account.balance -= amount;
await this.saveAccount(account);
return true;
}
return false;
} finally {
release();
}
}
// SICHER: Datenbanktransaktion für Atomarität
async withdrawWithTransaction(accountId, amount) {
return await this.db.transaction(async (trx) => {
// SICHER: Zeilensperren
const account = await trx('accounts')
.where({ id: accountId })
.forUpdate() // Zeile sperren
.first();
if (account.balance >= amount) {
await trx('accounts')
.where({ id: accountId })
.update({ balance: account.balance - amount });
return true;
}
return false;
});
}
// SICHER: Atomare Session-Erstellung
async createSession(userId) {
const release = await this.mutex.acquire();
try {
// Atomar prüfen und erstellen
for (const [id, session] of this.sessions) {
if (session.userId === userId) {
return session;
}
}
const session = { id: generateId(), userId };
this.sessions.set(session.id, session);
return session;
} finally {
release();
}
}
// SICHER: Atomare Redis-Operationen
async incrementCounter(key) {
// SICHER: INCR ist atomar in Redis
return await this.redis.incr(key);
}
}
// SICHER: Atomare Dateioperationen
const fs = require('fs').promises;
const lockfile = require('proper-lockfile');
async function writeFileSafe(filepath, content) {
// SICHER: Dateisperren
const release = await lockfile.lock(filepath);
try {
await fs.writeFile(filepath, content);
} finally {
await release();
}
}
// SICHER: C mit ordnungsgemäßer Synchronisierung
#include <pthread.h>
#include <stdatomic.h>
// SICHER: Atomarer Zähler
atomic_int safe_counter = 0;
void* increment_safe(void* arg) {
for (int i = 0; i < 1000000; i++) {
// SICHER: Atomares Inkrement
atomic_fetch_add(&safe_counter, 1);
}
return NULL;
}
// SICHER: Mutex für komplexe Operationen verwenden
pthread_mutex_t balance_mutex = PTHREAD_MUTEX_INITIALIZER;
double account_balance = 0;
int withdraw_safe(double amount) {
// SICHER: Sperren vor Check-and-Act
pthread_mutex_lock(&balance_mutex);
int success = 0;
if (account_balance >= amount) {
account_balance -= amount;
success = 1;
}
pthread_mutex_unlock(&balance_mutex);
return success;
}
// SICHER: TOCTOU mit ordnungsgemäßen Dateioperationen vermeiden
int access_file_safe(const char* filename) {
// SICHER: Erst öffnen, dann Eigentum prüfen
int fd = open(filename, O_RDONLY);
if (fd < 0) {
return -1;
}
struct stat st;
// SICHER: fstat auf geöffnetem Dateideskriptor
if (fstat(fd, &st) < 0) {
close(fd);
return -1;
}
if (st.st_uid != getuid()) {
close(fd);
return -1;
}
// Jetzt sicher fd zu verwenden
return fd;
}
Ausgenutzt in der Praxis
Double Spending
Race Conditions in Zahlungssystemen.
Privilegieneskalation
TOCTOU-Angriffe auf setuid-Programme.
Session-Hijacking
Race Conditions in Session-Management.
Werkzeuge zum Testen/Ausnutzen
-
Thread-Sanitizer (TSan).
-
Race-Condition-Fuzzer.
-
Statische Analyse für Nebenläufigkeitsfehler.
CVE-Beispiele
-
CVE-2016-2782: Kernel-TOCTOU-Race-Condition.
-
CVE-2019-15666: Race Condition in setuid.
Referenzen
-
MITRE. "CWE-662: Improper Synchronization." https://cwe.mitre.org/data/definitions/662.html
-
"The Art of Multiprocessor Programming."