Zurück zum Blog

Spectre-Seitenkanalangriffe auf serverlose Spiel-Backends: Ein Runbook zur Erkennung und Behebung

Veröffentlicht am 25. August 2026
Spectre-Seitenkanalangriffe auf serverlose Spiel-Backends: Ein Runbook zur Erkennung und Behebung Mit Hilfe von KI generiert

Kurz und knapp

Entdecken Sie, wie Spectre serverlose Spiel-Backends bedroht, und erfahren Sie konkrete Abhilfemaßnahmen wie Timer-Härtung und Prozessisolierung.

Ihr serverloses Spiel-Backend verarbeitet Spieler-Authentifizierungstokens, Inventaraktualisierungen und Matchmaking-Anfragen auf gemeinsam genutzter Hardware mit Dutzenden anderen Mandanten. Ein Angreifer, der auf derselben physischen CPU co-lokalisiert ist, kann sensible Daten extrahieren — nicht durch Ihren Code, sondern durch das Silizium selbst. Die jüngste Offenlegung von Cloudflare bestätigt, dass dies nicht theoretisch ist: Forscher konnten zuverlässig 12 Bits pro Sekunde mit 99% Genauigkeit aus Cloudflare Workers in Produktion extrahieren.

Wenn Sie Spiellogik auf einer serverlosen oder Multi-Tenant-Plattform ausführen, deckt dieses Runbook ab, was bricht, wie man es erkennt, wie man es behebt und wie man gegen Wiederholungen architekturiert.

Was bricht: Spectre in serverlosen Spiel-Backends

Die Angriffsfläche

Spectre nutzt spekulative Ausführung in modernen CPUs. Wenn ein Prozessor auf eine bedingte Verzweigung trifft, führt er beide Pfade spekulativ aus, bevor sich die Bedingung auflöst. Wenn die Spekulation falsch ist, rollt die CPU zurück — aber Spuren bleiben im CPU-Cache. Ein Angreifer, der Cache-Timing misst, kann ableiten, auf welche Daten während der Spekulation zugegriffen wurde.

In einer serverlosen Umgebung wird dies gefährlich, weil:

  • Gemeinsame Hardware: Ihre Spiel-Backend-Funktion läuft auf demselben physischen CPU-Kern (zu verschiedenen Zeiten) wie der Code anderer Mandanten
  • Hochauflösende Timer: performance.now() von JavaScript und SharedArrayBuffer-basierte Timer geben Angreifern Cache-Messungen mit Nanosekunden-Präzision
  • Vorhersehbare Speicherlayouts: Der JIT-Compiler von V8 erzeugt konsistente Speicherlayouts über Aufrufe hinweg, was Gadget-Ketten zuverlässig macht

Der Cloudflare-Workers-Proof-of-Concept

Das Forschungsteam der TU Graz hat in Zusammenarbeit mit Cloudflare eine praktische Angriffskette demonstriert:

  1. Gadget-Identifikation: Finden Sie ein Gadget für spekulative Ausführung in der V8-Laufzeit, das auf angreiferkontrollierten Speicher basierend auf geheimen Daten zugreift
  2. Timer-Einrichtung: Verwenden Sie SharedArrayBuffer, um einen Hochauflösungs-Timer zu erstellen (Sub-Nanosekunden-Präzision)
  3. Cache-Priming: Leeren Sie relevante Cache-Lines, lösen Sie das Gadget aus und messen Sie dann die Neuladezeiten
  4. Datenextraktion: Rekonstruieren Sie geheime Bits aus Timing-Messungen mit 12 Bits pro Sekunde bei 99% Genauigkeit

Die geleakten Daten umfassten Authentifizierungstokens, Verschlüsselungsschlüssel und andere Geheimnisse, die von co-lokalisierten Workern verarbeitet wurden.

Warum Spiel-Backends besonders verwundbar sind

Spiel-Backends verarbeiten kontinuierlich hochwertige Geheimnisse:

  • JWT-Tokens für die Spieler-Authentifizierung (typischerweise 300-1000 Bytes base64-kodierter Daten)
  • Sitzungsverschlüsselungsschlüssel für den Echtzeit-Multiplayer-Zustand
  • Zahlungsabwicklungstokens für In-App-Käufe
  • Anti-Cheat-Signaturen, die geheim bleiben müssen, um wirksam zu sein

Eine Leckrate von 12 Bit/s klingt langsam, aber ein 256-Bit-AES-Schlüssel benötigt nur ~21 Sekunden zur Extraktion. Ein 512-Bit-JWT-Token benötigt ~43 Sekunden. In einer Spielsitzung von über 20 Minuten kann ein Angreifer erhebliches geheimes Material extrahieren.

Wie man es erkennt

Überwachung auf Seitenkanalaktivität

Sie können Spectre-Angriffe nicht direkt durch Anwendungsprotokolle beobachten. Überwachen Sie stattdessen die Vorbedingungen und Verhaltenssignaturen:

1. Erkennung von Timer-Auflösungsmissbrauch

// Detection script: Monitor for high-frequency timer access patterns
// Deploy as a middleware or wrapper around your serverless functions

const TIMER_ACCESS_THRESHOLD = 1000; // accesses per second
const timerAccessLog = new Map();

function monitorTimerAccess(sessionId) {
  const now = Date.now();
  const entry = timerAccessLog.get(sessionId) || { count: 0, windowStart: now };
  
  if (now - entry.windowStart > 1000) {
    // Reset window
    entry.count = 1;
    entry.windowStart = now;
  } else {
    entry.count++;
  }
  
  timerAccessLog.set(sessionId, entry);
  
  if (entry.count > TIMER_ACCESS_THRESHOLD) {
    // Alert: Possible side-channel reconnaissance
    logSecurityEvent({
      type: 'TIMER_ABUSE_SUSPECTED',
      sessionId,
      accessCount: entry.count,
      timestamp: now,
      severity: 'HIGH'
    });
    return true; // Flag for further inspection
  }
  return false;
}

2. SharedArrayBuffer-Nutzungsüberwachung

Wenn Ihr Spiel-Backend SharedArrayBuffer nicht legitimerweise benötigt (die meisten tun das nicht), überwachen Sie dessen Erstellung:

// Wrap SharedArrayBuffer constructor to detect unauthorized usage
const OriginalSAB = globalThis.SharedArrayBuffer;
let sabCreationCount = 0;

globalThis.SharedArrayBuffer = function(...args) {
  sabCreationCount++;
  
  if (sabCreationCount > 5) { // Legitimate game code rarely creates many
    logSecurityEvent({
      type: 'SAB_CREATION_ANOMALY',
      count: sabCreationCount,
      stackTrace: new Error().stack,
      severity: 'CRITICAL'
    });
  }
  
  return new OriginalSAB(...args);
};

3. Cache-Timing-Musteranalyse

Überwachen Sie wiederholte Muster speicherintensiver Operationen, gefolgt von präzisen Timing-Messungen. Dies ist auf Anwendungsebene schwerer zu erkennen, aber Überwachung auf Infrastrukturebene kann Folgendes kennzeichnen:

  • Funktionen, die konsistent >90% ihrer zugewiesenen CPU-Zeit verbrauchen
  • Ungewöhnliche Muster von Atomics.load()- und Atomics.store()-Aufrufen
  • Funktionen, die auf große zusammenhängende Speicherbereiche ohne klaren Anwendungszweck zugreifen

Erkennung auf Infrastrukturebene

Auf Infrastrukturebene sollten Sie auf Folgendes achten:

  • Co-Location-Muster: Wenn dieselbe angreiferkontrollierte Funktion wiederholt auf derselben physischen Hardware wie Ihr Spiel-Backend landet, ist das eine rote Flagge
  • Ressourcenverbrauchsanomalien: Spectre-PoCs verbrauchen typischerweise 100% CPU auf dem Zielkern während der Messung
  • Netzwerk-Exfiltration: Die extrahierten Bits müssen das System irgendwie verlassen — überwachen Sie ungewöhnliche ausgehende Datenmuster von serverlosen Funktionen

Wie man es behebt

Sofortmaßnahmen (Bereitstellung innerhalb von 24 Stunden)

Schritt 1: Hochauflösende Timer deaktivieren

Die wirksamste Einzelmaßnahme ist, dem Angreifer die Fähigkeit zu nehmen, Cache-Timing präzise zu messen:

// serverless-security-hardening.js
// Apply to all game backend serverless functions

// 1. Reduce timer resolution to 100 microseconds (10,000x reduction)
if (typeof performance !== 'undefined') {
  const originalNow = performance.now.bind(performance);
  const TIMER_GRANULARITY = 0.1; // 100 microseconds
  
  performance.now = function() {
    const precise = originalNow();
    return Math.round(precise / TIMER_GRANULARITY) * TIMER_GRANULARITY;
  };
}

// 2. Disable SharedArrayBuffer entirely if not needed
// (Most game backends don't need it server-side)
delete globalThis.SharedArrayBuffer;
delete globalThis.Atomics;

// 3. Add timing jitter to all async operations
const originalSetTimeout = globalThis.setTimeout;
globalThis.setTimeout = function(callback, delay, ...args) {
  // Add random jitter between 0-5ms to prevent timing synchronization
  const jitter = Math.random() * 5;
  return originalSetTimeout(callback, delay + jitter, ...args);
};

Schritt 2: Prozessisolierung für sensible Operationen implementieren

Isolieren Sie Operationen, die Geheimnisse verarbeiten, in separaten Prozessen mit gehärteten Speicherlayouts:

// process-isolation-config.js
// Configuration for isolating sensitive game backend operations

const isolationConfig = {
  // Operations that MUST run in isolated processes
  sensitiveOperations: [
    'auth.token.verify',
    'auth.token.generate',
    'payment.process',
    'crypto.encrypt',
    'crypto.decrypt',
    'anticheat.signature.validate'
  ],
  
  // Process pool configuration
  processPool: {
    minProcesses: 2,
    maxProcesses: 8,
    // Each process gets its own memory space — no cross-process cache sharing
    memoryIsolation: true,
    // Randomize process assignment to prevent co-location targeting
    randomAssignment: true,
    // Rotate processes every N requests to disrupt long-running attacks
    rotationInterval: 1000
  }
};

// Implementation: Route sensitive operations to isolated processes
async function executeSensitiveOperation(operationName, payload) {
  if (!isolationConfig.sensitiveOperations.includes(operationName)) {
    throw new Error(`Operation ${operationName} not in sensitive list`);
  }
  
  const worker = await getIsolatedWorker(isolationConfig.processPool);
  
  try {
    const result = await worker.execute(operationName, payload);
    return result;
  } finally {
    // Always return worker to pool — never reuse across operations
    await worker.terminate(); // Fresh process next time
  }
}

Schritt 3: Speicherzugriffsmuster härten

Machen Sie geheimnisabhängige Speicherzugriffe konstant-zeitlich, um die Gadgets für spekulative Ausführung zu eliminieren:

// constant-time-comparison.js
// Replace all secret-dependent branching with constant-time operations

// VULNERABLE: Branch depends on secret data
function verifyTokenVulnerable(token, expectedHash) {
  const hash = computeHash(token);
  if (hash === expectedHash) { // Branch leaks information via cache
    return true;
  }
  return false;
}

// SECURE: Constant-time comparison — no branch depends on secret
function verifyTokenSecure(token, expectedHash) {
  const hash = computeHash(token);
  
  if (hash.length !== expectedHash.length) {
    return false; // Length mismatch is not secret-dependent
  }
  
  let result = 0;
  for (let i = 0; i < hash.length; i++) {
    // XOR accumulates differences without branching
    result |= hash.charCodeAt(i) ^ expectedHash.charCodeAt(i);
  }
  
  // Final comparison: 0 means all bytes matched
  return result === 0;
}

// SECURE: Constant-time array lookup (prevents cache-timing on index)
function constantTimeLookup(table, index) {
  // Access ALL entries, but only use the one we want
  // This prevents cache line reveals about which index was accessed
  let result = null;
  for (let i = 0; i < table.length; i++) {
    const match = (i === index) ? 0xFF : 0x00;
    // Conditional select without branching
    result = (table[i] & match) | (result & ~match);
  }
  return result;
}

Kurzfristige Maßnahmen (Bereitstellung innerhalb von 1 Woche)

Schritt 4: Token-Architektur mit Defense-in-Depth implementieren

Reduzieren Sie den Wert geleakter Daten, indem Sie minimieren, welche Geheimnisse im Speicher existieren:

// token-architecture.js
// Minimize secret material in serverless function memory

class SecureTokenHandler {
  constructor() {
    // Never store the full token — process in chunks
    this.CHUNK_SIZE = 32; // bytes
  }
  
  async verifyTokenChunked(token) {
    const chunks = this.splitIntoChunks(token);
    const expectedChunks = await this.getExpectedChunks(token.id);
    
    let isValid = true;
    for (let i = 0; i < chunks.length; i++) {
      // Each chunk verification is independent
      // Attacker must leak ALL chunks to reconstruct the token
      const chunkValid = await this.verifyChunk(chunks[i], expectedChunks[i]);
      isValid = isValid && chunkValid;
      
      // Immediately overwrite chunk in memory
      chunks[i].fill(0);
    }
    
    return isValid;
  }
  
  splitIntoChunks(token) {
    const buffer = Buffer.from(token, 'base64');
    const chunks = [];
    for (let i = 0; i < buffer.length; i += this.CHUNK_SIZE) {
      chunks.push(buffer.slice(i, i + this.CHUNK_SIZE));
    }
    return chunks;
  }
}

Schritt 5: Canary-Tokens bereitstellen

Platzieren Sie gefälschte Geheimnisse, die bei Zugriff Alarm auslösen:

// canary-tokens.js
// Deploy fake secrets that detect unauthorized memory reads

const CANARY_PREFIX = 'CANARY_';

function deployCanaryTokens() {
  const canaries = [];
  
  // Generate 10 fake tokens that look like real JWT tokens
  for (let i = 0; i < 10; i++) {
    const canary = {
      id: `${CANARY_PREFIX}${generateUUID()}`,
      value: generateFakeJWT(), // Looks real but is tracked
      deployedAt: Date.now(),
      location: `memory_region_${i}`
    };
    canaries.push(canary);
  }
  
  // Store in predictable memory locations
  // If these values appear in network traffic, we know memory was read
  globalThis.__SECURITY_CANARIES__ = canaries;
  
  return canaries;
}

function checkCanaryIntegrity() {
  const canaries = globalThis.__SECURITY_CANARIES__ || [];
  
  // Verify canaries haven't been exfiltrated by checking
  // if they appear in any outbound network requests
  // (This requires network monitoring integration)
  
  return canaries.every(c => {
    const age = Date.now() - c.deployedAt;
    return age < 3600000; // Rotate canaries every hour
  });
}

Wie man Wiederholungen verhindert

Architekturmuster für Spectre-resistente Spiel-Backends

Muster 1: Geheimnis-Minimierung

Die beste Verteidigung gegen Spectre ist, weniger Geheimnisse zum Leaken zu haben:

  • Verwenden Sie kurzlebige Tokens (5 Minuten Ablauf) statt langlebiger Sitzungstokens
  • Implementieren Sie Token-Binding an Client-IP/Fingerabdruck, sodass geleakte Tokens anderswo nutzlos sind
  • Speichern Sie Verschlüsselungsschlüssel in Hardware-Sicherheitsmodulen (HSMs), nicht im Speicher serverloser Funktionen
  • Verwenden Sie zustandslose Authentifizierung (signierte JWTs mit kurzem Ablauf), um serverseitige Sitzungsspeicherung zu vermeiden

Muster 2: Mehrschichtige Isolierung

┌─────────────────────────────────────────────────┐
│                  Load Balancer                    │
│         (Random assignment to regions)           │
├─────────────────────────────────────────────────┤
│           Serverless Function Layer              │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐      │
│  │ Function │  │ Function │  │ Function │      │
│  │    A     │  │    B     │  │    C     │      │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘      │
│       │              │              │            │
├───────┼──────────────┼──────────────┼────────────┤
│       ▼              ▼              ▼            │
│           Process Isolation Layer                │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐      │
│  │ Isolated │  │ Isolated │  │ Isolated │      │
│  │ Process  │  │ Process  │  │ Process  │      │
│  │ (Secrets)│  │ (Secrets)│  │ (Secrets)│      │
│  └──────────┘  └──────────┘  └──────────┘      │
│       Randomized memory layout per invocation    │
├─────────────────────────────────────────────────┤
│              HSM / Key Vault Layer               │
│         (Encryption keys never in memory)        │
└─────────────────────────────────────────────────┘

Muster 3: Timer-Härtung

Setzen Sie diese Maßnahmen auf Plattformebene um, nicht pro Funktion:

// platform-timer-hardening.js
// Apply at the serverless platform initialization layer

function hardenTimers() {
  // 1. Reduce performance.now() resolution
  const perfNow = performance.now;
  performance.now = () => Math.round(perfNow() / 100) * 100;
  
  // 2. Reduce Date.now() resolution  
  const dateNow = Date.now;
  Date.now = () => Math.round(dateNow() / 10) * 10;
  
  // 3. Disable SharedArrayBuffer
  globalThis.SharedArrayBuffer = undefined;
  
  // 4. Add noise to all timing sources
  const noise = () => Math.random() * 0.05; // 50 microsecond noise
  const originalPerfNow = performance.now;
  performance.now = () => originalPerfNow() + noise();
  
  // 5. Limit Worker thread creation
  const OriginalWorker = globalThis.Worker;
  let workerCount = 0;
  globalThis.Worker = function(...args) {
    if (workerCount >= 2) {
      throw new Error('Worker limit exceeded');
    }
    workerCount++;
    return new OriginalWorker(...args);
  };
}

Best Practices für die Sicherheit serverloser Spiel-Backends

  1. Co-Location annehmen: Entwerfen Sie Ihr System unter der Annahme, dass ein Angreifer Ihre Hardware teilt. Jedes Geheimnis im Speicher ist potenziell durch Seitenkanäle lesbar. Minimieren Sie, was zu einem bestimmten Zeitpunkt im Speicher existiert.

  2. Aggressiv rotieren: Verwenden Sie maximal 5 Minuten Token-Ablauf. Rotieren Sie Verschlüsselungsschlüssel stündlich. Das Verwundbarkeitsfenster ist direkt proportional dazu, wie lange Geheimnisse im Speicher verbleiben.

  3. Timer-Zugriffsmuster überwachen: Wenn Ihr Spiel-Backend keine Sub-Millisekunden-Timing benötigt (die meisten tun das nicht), deaktivieren Sie hochauflösende Timer vollständig. Wenn Sie sie für das Gameplay benötigen, isolieren Sie timing-sensitiven Code von geheimnisverarbeitendem Code.

  4. Mit Spectre-PoCs testen: Führen Sie Proof-of-Concept-Spectre-Angriffe gegen Ihre Staging-Umgebung aus. Das Cloudflare-Forschungspapier enthält Methoden, die Sie anpassen können. Wenn Sie Ihre eigenen Geheimnisse leaken können, kann das auch ein Angreifer.

  5. Verteidigung in Schichten aufbauen: Keine einzelne Maßnahme ist ausreichend. Kombinieren Sie Timer-Härtung, Prozessisolierung, Constant-Time-Code, kurzlebige Tokens und Canary-Erkennung. Jede Schicht erhöht die Angriffskosten exponentiell.

Der Ansatz von horizOn für serverlose Sicherheit

Bei horizOn verarbeiten wir diese Sicherheitsbedenken auf Plattformebene, sodass Sie nicht jede Maßnahme selbst implementieren müssen. Unsere serverlose Spiel-Backend-Infrastruktur umfasst Timer-Härtung, Prozessisolierung für sensible Operationen und automatische Token-Rotation — alles standardmäßig konfiguriert.

Wenn Sie Authentifizierung über horizOn abwickeln, werden Tokens in isolierten Prozessen mit Constant-Time-Vergleich verifiziert, und Verschlüsselungsschlüssel werden über unsere Key-Vault-Ebene verwaltet, statt im Funktionsspeicher gespeichert zu werden. Dadurch wird die Spectre-Angriffsfläche minimiert, ohne dass Sie benutzerdefinierten Sicherheitscode schreiben müssen.

Für Spieleentwickler, die Architekturen bauen, die Kompromittierungen überstehen, gilt dasselbe Prinzip: Gehen Sie von einem Angriff aus, minimieren Sie den Schadensradius und erkennen Sie früh.

Ihr nächster Schritt

Auditieren Sie Ihr serverloses Spiel-Backend noch heute. Beginnen Sie mit diesen drei Maßnahmen:

  1. Inventarisieren Sie Ihre Geheimnisse: Listen Sie jedes sensible Datum auf, das während einer typischen Spielsitzung im Speicher serverloser Funktionen existiert
  2. Messen Sie die Timer-Auflösung: Prüfen Sie, ob Ihre Plattform hochauflösende Timer exponiert (führen Sie performance.now() in einer Schleife aus und messen Sie das minimale Delta)
  3. Testen Sie die Constant-Time-Konformität: Überprüfen Sie Ihren Authentifizierungs- und Verschlüsselungscode auf geheimnisabhängige Verzweigungen

Die Spectre-Klasse von Angriffen wird nicht verschwinden — sie ist in die Funktionsweise moderner CPUs eingebrannt. Die Frage ist nicht, ob Ihr serverloses Backend theoretisch verwundbar ist, sondern ob Sie es Angreifern teuer genug gemacht haben, um anderswo nach Zielen zu suchen.


Quelle: Ein erneuter Blick auf Remote-Spectre-Angriffe auf Cloudflare Workers