● CWE-942 · OWASP A05:2021

CORS-Fehlkonfiguration (CWE-942): Wie gespiegelte Origins Daten preisgeben und wie man sie behebt

Eine API, die auf jede Origin mit Access-Control-Allow-Origin gleich dieser Origin und Access-Control-Allow-Credentials: true antwortet, lässt jede Website ihre Antworten mit den Cookies des Besuchers lesen. Wie Suffix-Prüfungen, die null-Origin und gespiegelte Origins die Same-Origin-Policy aushebeln, und welche Allowlist mit exakter Übereinstimmung das behebt, mit einem Beispiel in Express und cors.

Einfache Erklärung (ELI5)

Ein Bankangestellter liest Ihren Kontostand jedem vor, den Sie schicken, solange der Bote einen Brief zeigt, für wen er arbeitet, und die Liste des Angestellten diesen Namen erlaubt. Die Regel eines Angestellten lautet: „Jeden Namen erlauben, der auf das Wort Schmidt endet.“ Ein Fremder schickt einen Boten mit einem Brief von „Goldschmidt“, der Angestellte erlaubt ihn, und Ihr Kontostand wird jemandem vorgelesen, den Sie nie geschickt haben. Das Spiegeln der Origin ist die bequeme Variante, bei der der Angestellte jeden Namen erlaubt. Die Lösung ist eine kurze, exakte Namensliste, Buchstabe für Buchstabe verglichen.

Kernkonzepte & Begriffe

Same-Origin-Policy
Browser lassen ein Skript nur Antworten seiner eigenen Origin (Schema, Host und Port) lesen. CORS-Antwortheader sind der Weg, auf dem ein Server diese Regel bewusst für benannte Origins lockert.
Access-Control-Allow-Origin mit Credentials
Nennt die Antwort die Origin des Aufrufers in Access-Control-Allow-Origin und sendet Access-Control-Allow-Credentials: true, kann ein Skript auf dieser Origin Antworten auf Requests lesen, die die Cookies des Nutzers trugen. Browser lehnen * zusammen mit Credentials ab, deshalb spiegeln falsch konfigurierte Server die Origin.
Gespiegelte Origin und Suffix-Prüfung
Den Origin-Header in die Antwort zu kopieren vertraut jeder Website. Prüfungen wie endsWith('example.com'), Teilstring-Suchen oder reguläre Ausdrücke ohne Anker und mit unmaskierten Punkten akzeptieren auch https://attacker-example.com.
Die null-Origin
Sandbox-iframes, data:-URLs und manche Weiterleitungen senden Origin: null. Jeder Angreifer kann sie erzeugen, null zu erlauben heißt also, alle zu erlauben.
Vary: Origin
Hängt Access-Control-Allow-Origin vom Request ab, muss die Antwort Vary: Origin tragen, damit gemeinsame Caches die CORS-Header einer Origin nicht an eine andere ausliefern. Das Paket cors ergänzt ihn bei dynamischen Origins.

Schritt-für-Schritt Angriffsablauf

Schritt 1

Das Opfer ist bei der Website der API angemeldet

Sein Browser hält ein Sitzungscookie für api.example.com, das bei websiteübergreifenden Requests mitgesendet wird.

Schritt 2

Es öffnet eine Seite auf der Domain des Angreifers

Ein Skript ruft fetch('https://api.example.com/api/account', { credentials: 'include' }) auf. Eine Seite auf https://attacker-example.com, die nur die Länge der Antwort in der Konsole ausgibt, zeigt den Fall.

Schritt 3

Die API erlaubt die Origin

Die Suffix-Prüfung akzeptiert attacker-example.com, daher trägt die Antwort diese Origin in Access-Control-Allow-Origin zusammen mit Access-Control-Allow-Credentials: true.

Schritt 4

Das Skript liest private Daten

Der Browser übergibt das Konto-JSON an das Skript des Angreifers, das es überallhin senden kann. 2016 fand James Kettle dieses Muster bei mehreren Bitcoin-Börsen, wo es API-Schlüssel und Wallet-Daten offenlegte.

Quellcode: Verwundbar vs. Sicher

VERWUNDBARE IMPLEMENTIERUNG
// server.js: jede Origin auf example.com, dazu null, darf mit Cookies lesen
import express from 'express';
import cors from 'cors';
import { accountFor, requireSession } from './account.js';

const app = express();

// true lässt cors die Origin des Aufrufers samt Credentials spiegeln
app.use(cors({
  origin: (origin, callback) => callback(null, !origin || origin === 'null' || origin.endsWith('example.com')),
  credentials: true,
}));

// private Kontodaten, lesbar für die erlaubten Origins
app.get('/api/account', requireSession, (req, res) => {
  res.json(accountFor(req.session.userId));
});
GEHÄRTETER SICHERHEITS-PATCH
// server.js: zwei exakte Origins, sonst nichts
import express from 'express';
import cors from 'cors';
import { accountFor, requireSession } from './account.js';

const app = express();

// vollständige Origins mit Schema und Host, exakt verglichen
const ALLOWED_ORIGINS = new Set(['https://app.example.com', 'https://admin.example.com']);

// unbekannte oder fehlende Origin bekommt keine CORS-Header; cors ergänzt Vary: Origin
app.use(cors({
  origin: (origin, callback) => callback(null, origin !== undefined && ALLOWED_ORIGINS.has(origin)),
  credentials: true,
  methods: ['GET', 'POST'],
  maxAge: 600,
}));

// personenbezogene Daten landen nie in gemeinsamen Caches
app.get('/api/account', requireSession, (req, res) => {
  res.set('Cache-Control', 'no-store');
  res.json(accountFor(req.session.userId));
});

Checkliste für Engineering & Systemsicherheit

Quellen

← Zum vollständigen Sicherheitsverzeichnis Alle Anleitungen zu Schwachstellen →