CORS Security

CORS (Cross-Origin Resource Sharing) ist ein von modernen Browsern implementierter Sicherheitsmechanismus, der steuert, wie Webanwendungen HTTP-Anfragen an Domains stellen können, die sich von derjenigen unterscheiden, die die ursprüngliche Seite ausgeliefert hat, und der die Same-Origin Policy (SOP) auf kontrollierte Weise lockert - eine grundlegende Sicherheitsrichtlinie, die verhindert, dass Skripte einer Herkunft auf Ressourcen einer anderen Herkunft zugreifen. Obwohl CORS für moderne Webanwendungsarchitekturen unerlässlich ist, in denen Front-Ends häufig APIs konsumieren müssen, die auf unterschiedlichen Domains gehostet werden, stellt seine unsachgemäße Konfiguration eine der häufigsten und gefährlichsten Schwachstellen in heutigen Webanwendungen dar. CORS-Fehlkonfigurationen können sensible Daten gegenüber nicht autorisierten Domains offenlegen, Cross-Site-Request-Forgery-Angriffe (CSRF) selbst bei Vorhandensein von Anti-CSRF-Tokens ermöglichen, den Diebstahl von Anmeldedaten erleichtern und es Angreifern in Extremfällen ermöglichen, privilegierte Aktionen im Namen authentifizierter Benutzer auszuführen. Das Problem wird durch die Tatsache verschärft, dass viele Entwickler bei CORS-Fehlern während der Entwicklung zu übermäßig permissiven Lösungen greifen (etwa der Verwendung der Wildcard "*" oder der automatischen Spiegelung der Anfrage-Herkunft), ohne die Sicherheitsimplikationen vollständig zu verstehen. Dieser Artikel untersucht eingehend die Grundlagen von CORS, häufige Konfigurationsschwachstellen und etabliert robuste Praktiken für eine sichere Implementierung über verschiedene Plattformen und Frameworks hinweg, wobei Funktionalität und eine angemessene defensive Haltung in Einklang gebracht werden.

Same-Origin Policy (SOP)

Browser implementieren die SOP: Skripte können nur auf Ressourcen derselben Herkunft zugreifen (Protokoll + Domain + Port). CORS lockert die SOP auf kontrollierte Weise.

      # Gleiche Herkunft
      https://example.com/api ← https://example.com/app [OK]
      # Unterschiedliche Herkünfte (durch SOP blockiert)
      https://example.com ← http://example.com (Protokoll)
      https://example.com ← https://api.example.com (Subdomain)
      https://example.com ← https://example.com:8080 (Port)
      

CORS Headers

Access-Control-Allow-Origin

      # Bestimmte Herkunft erlauben (empfohlen)
      Access-Control-Allow-Origin: https://trusted.com
      # Jede Herkunft erlauben (GEFÄHRLICH!)
      Access-Control-Allow-Origin: *
      # Dynamisch basierend auf Whitelist (korrekt)
      const allowedOrigins = ['https://app1.com', 'https://app2.com'];
      const origin = request.headers.origin;
      if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      

Weitere Wichtige Headers

      # Anmeldedaten erlauben (Cookies, Auth-Header)
      Access-Control-Allow-Credentials: true
      # Erlaubte HTTP-Methoden
      Access-Control-Allow-Methods: GET, POST, PUT, DELETE
      # Erlaubte Header in Anfragen
      Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
      # Für clientseitiges JavaScript freigegebene Header
      Access-Control-Expose-Headers: X-Custom-Header, X-Request-Id
      # Cache-Dauer des Preflights (Sekunden)
      Access-Control-Max-Age: 86400
      

Preflight Requests

Browser senden vor „nicht einfachen“ Anfragen eine OPTIONS-Anfrage, um Berechtigungen zu überprüfen.

      # Client sendet den Preflight
      OPTIONS /api/resource HTTP/1.1
      Origin: https://app.com
      Access-Control-Request-Method: DELETE
      Access-Control-Request-Headers: Authorization
      # Server antwortet mit Berechtigungen
      HTTP/1.1 204 No Content
      Access-Control-Allow-Origin: https://app.com
      Access-Control-Allow-Methods: GET, POST, DELETE
      Access-Control-Allow-Headers: Authorization
      Access-Control-Max-Age: 86400
      

Häufige CORS-Schwachstellen

1. Wildcard mit Anmeldedaten

      # [FEHLER] VERWUNDBAR - funktioniert nicht und ist gefährlich
      Access-Control-Allow-Origin: *
      Access-Control-Allow-Credentials: true
      # Browser blockieren diese Kombination
      # [OK] KORREKT - bestimmte Herkunft mit Anmeldedaten
      Access-Control-Allow-Origin: https://trusted.com
      Access-Control-Allow-Credentials: true
      

2. Reflection Attack

      # [FEHLER] VERWUNDBAR - spiegelt jede origin
      const origin = request.headers.origin;
      res.setHeader('Access-Control-Allow-Origin', origin);
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      # [OK] KORREKT - Whitelist-Validierung
      const allowedOrigins = ['https://app.com', 'https://admin.com'];
      const origin = request.headers.origin;
      if (allowedOrigins.includes(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      res.setHeader('Access-Control-Allow-Credentials', 'true');
      }
      

3. Subdomain Wildcard

      # [FEHLER] VERWUNDBAR - schlecht implementierte Regex
      const origin = request.headers.origin;
      if (/https:\/\/.*\.example\.com/.test(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      // Akzeptiert https://evil.example.com.attacker.com
      # [OK] KORREKT - strikte Validierung
      const origin = request.headers.origin;
      if (/^https:\/\/[a-z0-9-]+\.example\.com$/.test(origin)) {
      res.setHeader('Access-Control-Allow-Origin', origin);
      }
      

Sichere Konfiguration nach Technologie

Node.js/Express (CORS middleware)

      const cors = require('cors');
      // Sichere Konfiguration
      const corsOptions = {
      origin: function (origin, callback) {
      const allowedOrigins = [
      'https://app.example.com',
      'https://admin.example.com'
      ];
      if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
      } else {
      callback(new Error('Not allowed by CORS'));
      }
      },
      credentials: true,
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Content-Type', 'Authorization'],
      maxAge: 86400
      };
      app.use(cors(corsOptions));
      

Nginx

      # Bedingte Konfiguration
      map $http_origin $cors_origin {
      default "";
      "~^https://app\\.example\\.com$" $http_origin;
      "~^https://admin\\.example\\.com$" $http_origin;
      }
      server {
      location /api {
      if ($cors_origin != "") {
      add_header Access-Control-Allow-Origin $cors_origin always;
      add_header Access-Control-Allow-Credentials true always;
      add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
      add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
      }
      if ($request_method = OPTIONS) {
      return 204;
      }
      }
      }
      

Apache

      # .htaccess
      SetEnvIf Origin "^https://(app|admin)\\.example\\.com$" CORS_ORIGIN=$0
      Header always set Access-Control-Allow-Origin "%e" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Credentials "true" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE" env=CORS_ORIGIN
      Header always set Access-Control-Allow-Headers "Authorization, Content-Type" env=CORS_ORIGIN
      # Auf OPTIONS-Preflight antworten
      RewriteEngine On
      RewriteCond % OPTIONS
      RewriteRule ^(.*)$ $1 [R=204,L]
      

Testing CORS

      # Test mit curl
      curl -H "Origin: https://evil.com" \\
      -H "Access-Control-Request-Method: DELETE" \\
      -H "Access-Control-Request-Headers: Authorization" \\
      -X OPTIONS \\
      https://api.example.com/resource
      # JavaScript-Test
      fetch('https://api.example.com/data', {
      method: 'GET',
      credentials: 'include',
      headers: {
      'Content-Type': 'application/json'
      }
      }).then(response => console.log(response));
      

Best Practices

  • Verwenden Sie niemals die Wildcard (*) in APIs mit sensiblen Daten
  • Explizite Whitelist der erlaubten Herkünfte
  • Strikte Validierung der Herkunft mit sicherer Regex
  • Minimieren Sie credentials: Nur aktivieren, wenn wirklich notwendig
  • Least privilege: Nur die notwendigen Methoden und Header erlauben
  • Cache preflight: Verwenden Sie Max-Age, um den Overhead zu reduzieren
  • Monitoring: Protokollieren Sie verdächtige CORS-Ablehnungen

Checkliste für sicheres CORS

  • [OK] Herkunft gegen eine explizite Whitelist validiert
  • [OK] Validierungs-Regex erlaubt keine Bypasses
  • [OK] Credentials nur bei Bedarf aktiviert
  • [OK] Methoden und Header auf das Minimum beschränkt
  • [OK] Preflight korrekt konfiguriert
  • [OK] Gegen bösartige Herkünfte getestet
  • [OK] Logs abgelehnter Anfragen überwacht