HOMEBlog goSecurity, Informationssicherheit, Web SecurityCross-Origin Resource Sharing (CORS)

Cross-Origin Resource Sharing (CORS)

Webapplikationen sind aus unserem digitalen Alltag fast nicht mehr wegzudenken. Ob man ihnen bei der Zeiterfassung, beim Online Shopping oder durch Soziale Medien begegnet, früher oder später läuft man ihnen über den Weg. Auch der Aufgabenbereich von Webapplikationen hat sich über die Jahre mehr und mehr ausgeweitet. Schon lange werden durch Webapplicationen nicht nur statische HTML-Seiten aufgerufen, sondern es sind ganze Frameworks im Einsatz, um unsere Daten zu verarbeiten und zu speichern. Deshalb sind sie auch ein beliebtes Ziel für Hacker, denn bei einem Grossteil von Schwachstellen handelt es sich um ganz banale Miskonfigurationen, die ausgenutzt werden können.

Alle Paar Jahre veröffentlicht die OWASP Foundation [1], welche sich für mehr Sicherheit in Webapplikationen einsetzt, ihre “Top Ten” Liste der kritischsten und meist verbreitetsten Sicherheitslücken in Webapplikationen [2]. Das oben erwähnte Problem der Miskonfiguration befindet sich auf Platz 5 und wurde in der Ausarbeitung der Liste im Jahr 2021 in 4% der heutigen Webapplikationen gefunden.

Rang Security Risk
1. Broken Access Control
2. Cryptographic Failures
3. Injection
4. Insecure Design
5. Security Misconfiguration
6. Vulnerable and Outdated Components
7. Identification and Authentification Failures
8. Software and Data Integrity Failures
9. Security Logging and Monitoring Failures
10. Server-Side Request Forgery

Natürlich setzt sich dieser Punkt aus sehr vielen verschiedenen Miskonfigurationen zusammen, aber in diesem Blog möchte ich spezifisch auf CORS-Miskonfigurationen eingehen. Worum handelt es sich bei CORS genau? Welche Miskonfigurationen können potenziell entstehen und welche Auswirkungen haben diese auf die Sicherheit einer Webapplikation?

Die meisten Webentwickler kennen CORS nur als einen mühsamen Fehler, welcher verhindert, dass Bilder und andere Ressourcen im Browser nicht richtig angezeigt werden. CORS kann auch ein Spielverderber sein, wenn man von der eigenen Webapplikation auf wichtige APIs zugreifen will. Aus eigener Erfahrung kann dies besonders mühsam sein, wenn wir für unsere goAware Phishing-Simulationen die Ressourcen (wie Bilder, Fonts oder CSS-Styles) einer Webseite nicht dynamisch für unsere Phishing Seiten “ausleihen” können.

Ein kleines Beispiel: Nehmen wir an, ich möchte von meiner lokal gehosteten Webapplikation auf Port 5500 auf meine eigene API zugreifen. Diese API stelle ich über Port 3000 ebenfalls lokal auf meiner Maschine zur Verfügung. Ohne weitere Konfiguration werde ich prompt blockiert und kann meine eigene API nicht testen. In der Console meines Webbrowsers werde ich mit folgender Mitteilung konfrontiert:

Solche Fehlermeldungen haben auch mir in der Vergangenheit bereits viele Kopfschmerzen bereitet. In der Fehlermeldung ist von “CORS Header” und “Same Origin Policy” die Rede. Um CORS zu verstehen, müssen wir uns also zuerst mit der “Same Origin Policy” befassen, welche hier die Rolle des Spielverderbers einnimmt.

1. Was ist die Same Origin Policy (SOP)?

Die Same Origin Policy wurde im Jahr 1995 zu Zeiten des Netscape-Browsers erschaffen. Diese Policy war eine Reaktion auf eine Sicherheitsschwachstelle, bei der Webseiten in einem Browser auf die lokalen Daten auf dem Computer des Users aber auch auf die Daten von anderen Webseiten zugreifen konnten, wenn diese gleichzeitig in unterschiedlichen Frames im Browser geladen wurden. Die Idee der Same Origin Policy war es also zu verhindern, dass fremde Domänen auf die Ressourcen (und somit auch auf die vertraulichen Daten) anderer Domänen zugreifen können. Ressourcen dürfen nur von der eigenen, also Domänen mit dem gleichen Origin, aufgerufen werden. Diese Policy ist standardmässig seit 1995 in allen Browsern integriert.

Wenn man als Benutzer also beispielweise gleichzeitig auf einer Social Media Platform und dem E-Banking angemeldet ist, kann nur die Domäne der Bank selbst auf deren Ressourcen zugreifen.

Same Origin Policy im Einsatz

  1. Die Applikation bank.ch schickt eine Anfrage an das Backend bei bank.ch, um die eigenen Daten zu laden (beispielsweise um diese dem User in einem Dashboard zu präsentieren). In diesem Beispiel sind diese Daten auf dem Server unter bank.ch/data abgelegt.

  2. Der vom Server empfangene Request kommt vom Origin bank.ch. Durch die Same Origin Policy wird dieser Request durch den Server bank.ch akzeptiert. Der Server schickt eine passende Antwort auf die Anfrage.

  3. Bei einem Request von der Applikation socialmedia.com an bank.ch werden die gleichen Daten unter bank.ch/data abgefragt.

  4. Der Origin socialmedia.com wird von der Same Origin Policy durch den Server bank.ch jedoch nicht akzeptiert. Der Server bank.ch schickt aufgrund des Verstosses gegen diese Policy keine Antwort oder eine Fehlermeldung. Die angefragten Daten werden nicht an Dritte übermittelt.

 

Dies ist grundsätzlich eine sehr einfache Lösung um sicherzustellen, dass der Server keine sensiblen oder persönlichen Daten (zum Beispiel den aktuellen Kontostand) an andere Domänen schickt, welche diese Informationen nicht erhalten sollten. Die Same Origin Policy ist aber sehr restriktiv. Dies wird spätestens dann klar, wenn wir uns wieder dem Beispiel der lokal gehosteten Webapplikation widmen.

In dem Beispiel möchten wir vom Frontend (lokal gehostet auf Port 5500) auf unsere API zugreifen (lokal gehostet auf Port 3000). Als praktisches Beispiel für das Frontend erstellen wir eine sehr simple Client-Webseite. Diese enthält nur einen einfachen Button “Click me!”, welcher bei einem Klick eine Anfrage an unsere API (http://localhost:3000/api) sendet. Im Codebeispiel unten wird bei einem Klick also die Funktion makeApiRequest() auf Zeile 5 ausgeführt. Diese Funktion zeigt bei einem erfolgreichen API-Call die Nachricht des Servers auf der Seite an (Zeile 10-14). Zur Veranschaulichung der Beispiele werden auch die Response-Header auf der Webseite angezeigt. Bei einem Fehler wird eine Fehlermeldung angezeigt (Zeile 16-17).

 

Webapp Frontend auf Port 5500

Unser API Backend wiederum besteht aus einem einfachen Node.js Endpoint. Die API kann über die URL http://localhost:3000/api aufgerufen werden. Wird diese aufgerufen, geben wir als Antwort die Meldung “A successfull API call!” zurück um zu bestätigen, dass der Aufruf funktioniert hat. In einem Browser kann dies direkt ohne Einschränkung durch die Same Origin Policy getestet werden. Denn diese greifen im Browser selbst nicht und sind nur für die Kommunikation zwischen Webapplikationen gedacht.

Rest API Endpoint auf Port 3000.

Wenn wir unsere zwei Applikationen starten und den Button im Frontend anklicken, bekommen wir nun aber eine Fehlermeldung und den bekannten Fehler in der Console. Denn der Aufruf der API wird nun nicht durch den Browser selbst getätigt, sondernn durch die Webapplikation auf Port 5500, wodurch die Single Origin Policy zum Zug kommt.

Cross-Origin Request Blocked Fehler

Unter der Regel der Same Origin Policy sind unsere zwei Applikationen nämlich unterschiedliche Origins, weshalb die Abfrage untersagt wird.

Same Origin Policy verhindert API Zugriff
  1. Die Applikation auf localhost:5500 sendet einen Request an die API-Applikation unter localhost:3000. Es handelt sich durch die unterschidlichen Ports um unterschiedliche Origins.

  2. Der Origin von Port 5500 wird durch die API auf Port 3000 nicht durch die Same Origin Policy akzeptiert. Der Server schickt keine Antwort oder eine Fehlermeldung.

 

Ob es sich bei zwei URIs um den gleichen Origin handelt oder nicht wird durch 3 Faktoren bestimmt:

  1. Haben die beiden URIs das gleiche Schema (beide HTTP oder HTTPS)?

  2. Haben die beiden URIs die gleiche Domäne (inklusive gleiche Subdomänen)?

  3. Haben die beiden URIs den gleichen Port? Wird kein Port explizit angegeben, gilt der standardmässige Port für das Schema, also 80 oder 443.

In unserem Beispiel scheitern wir also im Punkt 3, da die beiden Applikationen auf unterschiedlichen Ports gehostet werden. Wie kann man dieses Problem nun umgehen? Besonders für moderne Webapplikationen ist es immerhin wichtig, dass sie unter Umständen Anfragen an andere Webapplikationen im Namen des Users senden können (zum Beispiel um eine authorisierte Zahlung durchzuführen). Hier kommt nun das Cross-Origin Resource-Sharing (CORS) zum Einsatz.

2. Was ist Cross-Origin Resource Sharing (CORS)?

Eine Möglichkeit die oben genannte Single Origin Policy sicher zu umgehen, ist die Implementation von CORS. Sie erlaubt es serverseitig bestimmte Origins zu konfigurieren, welche die Single Origin Policy umgehen dürfen und somit die Ressourcen des Servers laden dürfen. Oft werden diese von Webapplikationen aber zu locker definiert, um sicherzustellen, dass die Funktionalität der Webseite auf alle Fälle gewährleistet ist. Dadurch können sich unerwünschte Schwachstellen einfach einschleichen. Die Konfiguration solcher Origins wird serverseitig mittels dem. Access-Control-Allow-Origin  Access-Control-Allow-Origin Header gemacht. Eine Webapplikation kann also konfigurieren, welchen Origins sie explizit vertraut und welche ihre öffentlichen Ressourcen im Browser laden dürfen. Diese Konfigurationen werden über die HTTP-Response-Header an den Client kommuniziert. Wir können also unsere API so konfigurieren, dass sie diesen Header für unser Frontend folgendermassen festlegt:

Same Origin Policy wird durch CORS umgangen
  1. Die Applikation auf localhost:5500 sendet einen Request an die API-Applikation unter localhost:3000. Es handelt sich durch die unterschidlichen Ports um unterschiedliche Origins.
  2. Die API-Applikation ist so konfiguriert, dass das Cross-Origin Resource Sharing für den Origin localhost:5500 erlaubt ist. Dieser Origin darf also auf die Ressourcen der API zugreifen. Folgend wird also vom Server eine Antwort an den Client gesendet. Der Access-Control-Allow-Origin wiederspiegelt die CORS-Konfiguration des Servers.

In unserem Beispiel-Code kann dies zum Beispiel folgendermassen implementiert werden. Die genaue Implementation hängt natürlich vom jeweiligen Framework der Webapplikation ab. Durch die Verwendung des npm-Moduls cors (Zeile 2 und Zeile 6) können wir sämtlichen Domänen erlauben auf unsere API zuzugreifen. In der Praxis wird der Server dann den Header Access-Control-Allow-Origin: * an den Client zurücksenden.

CORS Implementierung mit Zugriff von beliebigen Origins
Es ist aber auch möglich diese Konfiguration etwas weiter einzuschränken. Wir definieren auf den Zeilen 6-9 in unserem Backend explizit unsere Frontend Webapp (http://localhost:5500) als erlaubter Origin. Bei einem Klick auf den Button in unserer Webapplikation auf Port 5500 wird nun der API-Call erlaubt und korrekt durchgeführt. Hosten wir unser Frontend auf Port 5555, können wir wieder nicht auf die API zugreifen.
Alternative CORS Implementierung mit Zugriff nur von einem bestimmten Origin (Port 5500 aber nicht Port 5555)

In diesem Fall können wir nun also erfolgreich von unserem Frontend (Port 5500) auf unsere API (Port 3000) zugreifen. Wenn man nun nur dieses Beispiel betrachtet, scheint der Ansatz mit dem Access-Control-Allow-Origin Header eine gute Lösung zu sein. Dies kommt aber daher, dass unser Beispiel etwas zu simpel ist. “Echte” Webapplikationen müssen oft mehreren Subdomänen und externen Domänen vertrauen und ihnen Zugriff auf unterschiedliche Ressourcen geben. Solch eine Konfiguration kann jedoch über einen simplen Header nicht so einfach gesetzt werden.

Der Access-Control-Allow-Origin Header kann nämlich nur auf die folgenden 3 Arten gesetzt werden [3]:

 

Gesetzter Header Beschreibung
 Access-Control-Allow-Origin: *

 

Beliebige Origins dürfen auf die Ressourcen der Applikation zugreifen.
Die Übertragung von Credentials wird jedoch bei der Wildcard * nicht erlaubt.
 Access-Control-Allow-Origin: <origin>

 

Ein einzelner Origin kann angegeben werden. Eine Liste von Origins wie Access-Control-Allow-Origin: http://localhost:5500, http://localhost:8080 ist aber beispielsweise nicht möglich! Auch sind keine Wildcards wie Access-Control-Allow-Origin: http://*.localhost:5500 möglich.
 Access-Control-Allow-Origin: null

 

Der Origin null kann auf Ressourcen zugreifen. Diese Einstellung sieht auf den ersten Blick vielleicht sicher aus. Jedoch werden beispielsweise sandboxed iframes mit diesem Origin ausgestattet. Zusätzlich ist es mit dieser Konfiguration (anders als beim Wildcard-Origin *) auch erlaubt Credentials zu übertragen. Diese Konfiguration des Headers sollte also nicht benutzt werden.

Neben dem Access-Control-Allow-Origin-Header gibt es noch weitere Header, welche mit CORS in Verbindung stehen. Diese möchte ich vollständigkeitshalber erwähnen, aber nicht tiefer darauf eingehen.

Header Mögliche Optionen Hinweis
 Access-Control-Allow-Credentials true

 

Dürfen Credentials über in Cross-Origin Requests mitgegeben werden.

 

 Access-Control-Allow-Headers

 

* / <header-name> / <header-name>, <header-name>, ... Gibt eine Liste von Headers zurück, welche bei einem Request erlaubt sind.

 

 Access-Control-Request-Methods * /  <method> / <method>, <method>, ... Gibt eine Liste von Methoden (PUT, DELETE, etc.) zurück, welche bei einem Request erlaubt sind. GET, HEAD und POST sind immer erlaubt.
 Access-Control-Expose-Header

 

 

 

* / <header-name> / <header-name>, <header-name>, ...

 

 

Gibt eine Liste von Methoden an, die durch Skripte im Browser bei einem Cross-Origin Request ausgelesen werden können. Standardmässig sind das Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified und Pragma

 

Im Unterschied zum Access-Control-Allow-Origin-Header können bei diesen Headern auch kommagetrennte Listen von Headern oder Methodennamen gesetzt werden. Beim Access-Control-Allow-Origin-Header aber können mehrere Origin zusammen nur mittels * freigegeben werden, oder nur ein einzelner Origin mittels <origin>. Diese Einschränkung in der Definition des Access-Control-Allow-Origin-Headers führt dazu, dass viele Entwickler diesen Header nicht einfach statisch setzen können. Ein Origin ist für moderne Webapplikationen oft nicht realistisch und die Einstellung alle Origins zuzulassen, ist oft zu grosszügig. Die einzige Lösung um mehrere Origins freizugeben, ist durch eine dynamische Generierung des Access-Control-Allow-Origin Headers, oft basierend auf dem Origin-Header Input des Users. Dies bedeutet, dass die Webapplikation, welche eine Anfrage erhält, den Origin Header je nach Request (und somit Input des Users) programmatisch setzt. Und genau hier steckt der Teufel im Detail.

3. CORS-Vulnerabilities

Wo liegen also nun genau die konkreten Gefahren bei einem falsch gesetzten oder dynamisch generierten Access-Control-Allow-Origin-Header. Im Folgenden möchte ich einige Varianten veranschaulichen wie dies umgesetzt werden kann und wie diese Implementationen konkret ausgenutzt werden könnten.

3.1. Reflektierter Origin

Die einfachste und kritischste Art um den dynamischen Header serverseitig zu generieren, ist einfach den vom Client angegebenen Origin-Header zurückzugeben.

In unsererm Code Beispiel könnte dies zum Beispiel in der API-Applikation so konfiguriert werden. Dabei wird in den corsOptions auf Zeile 7-9 anstelle eines fixen Origins eine Funktion hinterlegt, was bedeutet, dass der Origin des Clients dynamisch durch diese Funktion evaluiert wird. In diesem Beispiel geben wir durch den zweiten Parameter true im Callback auf Zeile 8 an, dass jeder Origin ohne weitere Prüfung akzeptiert wird.

Dynamische Evaluierung des Client Origins, welche den Origin jeweils reflektiert

Dies bedeutet, dass effektiv jede Domäne auf die Ressourcen der Applikation, falls nicht weiter geschützt, zugreifen könnte. Diese Implementation kann von einem Angreifer sehr schnell identifiziert und ausgenutzt werden. Hier ein fiktives Beispiel, wie ein solcher Angriff ablaufen könnte.

Ausnutzung einer reflektierten Origin Fehlkonfiguration
  1. Ein Benutzer meldet sich auf einer Webseite (vulnshop.com) mit einer CORS Schwachstelle an. Diese Seite ist wie oben im Beispiel so konfiguriert, dass jeder Origin im Access-Control-Allow-Origin reflektiert wird. Im Browser des Users wird das SessionCookie nach der Authentifizierung gespeichert.
  2. Ein Angreifer bringt den Benutzer dazu, eine durch ihn kontrollierte Seite hacker.com zu besuchen (z.B. durch das Zusenden eines Links in einem E-Mail). Auf dieser Seite ist ein Script eingebaut, welches automatisiert Anfragen im Namen des Benutzers an die vulnshop.com Seite macht. Kann das Cookie der unsicheren Webseite beispielsweise durch eine weitere Schwachstelle ausgelesen werden, kann noch ein viel mächtigerer Zugriff auf die Daten des Benutzers stattfinden. Wir nehmen mal an, dass dies möglich ist.

  3. Der Angreifer kann nun auf öffentliche, und durch das geklaute Cookie auch auf private Ressourcen des Benutzers (z.B. unter http://vulnshop.com/getPrivateData) zugreifen.

  4. Der Server vulnshop.com vertraut durch die Fehlkonfiguration der CORS-Einstellungen dem Origin hacker.com und sendet dem Angreifer die gewünschten Daten des Users.

Aus diesem Beispiel geht hervor wie kritisch eine solche Miskonfiguration unter Umständen sein kann. Zudem kann ein Angreifer sehr einfach herausfinden (meistens in 2-3 Anfragen), ob eine Webseite von dieser Fehlkonfiguration betroffen ist.

3.2. Origin Whitelist

Eine andere naheliegende Implementation um dem Access-Control-Allow-Origin-Header dynamisch zu generieren, ist eine Whitelist zu verwenden. Dabei ist auf dem Server eine Liste von Origins hinterlegt, welche auf die Ressourcen der Applikation zugreifen dürfen. Bei einem Request wird der Wert im Origin-Header mit den Origins in der Whitelist überprüft. Auch in einem solchen Fall können jedoch Fehler auftreten, dies vor allem in der Art wie der Vergleich der Origins implementiert ist.

Für die Vergleiche werden oft sogenannte “Regular Expressions” oder “Regex” benutzt. Diese definieren wie eine erlaubte Domäne geschrieben werden soll. So kann der Regex beispielsweise so konfiguriert sein, dass die erlaubten Origins explizit “vulnshop.com” enthalten müssen. Damit können beispielsweise alle Subdomänen einer Domäne inbegriffen werden.

Eine mögliche Implementation könnten wir in unserer API zum Beispiel folgendermassen implementieren. Für dieses Beispiel verwenden wir die Domain-Namen hacker.com sowie hackervulnshop.com für unser Frontend und möchten von da aus auf die API zugreifen.

Dynamische Evaluierung des Client Origins, welche Origins mit “vulnshop.com” zulässt
Ausnutzung einer Origin Whitelist Fehlkonfiguration
  1. Ein Benutzer meldet sich auf einer Webseite (vulnshop.com) mit einer CORS Schwachstelle an. Diese Seite ist wie oben im Beispiel so konfiguriert, dass jeder Origin mit “vulnshop.com” im Origin akzeptiert wird. Im Browser des Users wird das SessionCookie nach der Authentifizierung gespeichert.
  2. Ein Angreifer bringt den Benutzer dazu, eine durch ihn kontrollierte Seite zu besuchen (z.B. durch das Zusenden eines Links in einem E-Mail). Für dieses Beispiel sind das zwei verschiedene Seiten (hacker.com und hackervulnshop.com), welche im Besitz von zwei Hackern sind. Auf diesen Seiten ist jeweils ein Script eingebaut, welches automatisiert Anfragen im Namen des Benutzers an die vulnshop.com Seite macht. Kann das Cookie der unsicheren Webseite beispielsweise durch eine weitere Schwachstelle ausgelesen werden, kann noch ein viel mächtigerer Zugriff auf die Daten des Benutzers stattfinden. Wir nehmen mal an, dass dies möglich ist.

  3. Anfragen des Origins hacker.com werden durch die CORS Konfiguration des Servers blockiert, da der Origin nicht “vulnshop.com” enthält.

  4. Entsprechend sendet der Server keine weiteren Informationen an den hacker.com Client.

  5. In unserem Beispiel hat der Angreifer aber eine weitere Webseite registriert, um eine mögliche Miskonfiguration auszunutzen. Mit dem Origin hackervulnshop.com startet der Angreifer einen weiteren Angriffsversuch nachdem der Benutzer auf die Seite gelockt wurde.

  6. Da der neue Origin hackervulnshop.com das Wort “vulnshop.com” enthält, wird der Origin akzeptiert.

Um dieses Szenario auszunutzen, kann ein Angreifer aber das gleiche Spiel wie oben beschrieben mit der selbstregistrierten Domäne hackervulnshop.com durchführen. Das gleiche gilt auch für Regex, welche sicherstellen, dass die erlaubten Origins mit “vulnshop.com” beginnen oder enden müssen. In diesem Fall kann beispielsweise die Domäne vulnshop.com.hacker.com die Whitelist umgehen.

3.3. Null Origin

Ein besonders fataler Fehler ist, den null-Origin selbst whitezulisten. Wie bereits erwähnt, werden beispielsweise sandobxed iframes mit diesem Origin ausgestattet. Dies bedeutet, dass jede Webseite durch das Einbauen eines solchen iframes den null-Origin verwenden kann. Das Whitelisten eines null-Origins hat also effektiv den gleichen Effekt wie Access-Control-Allow-Origin: *. Zusätzlich werden aber die Sicherheitsvorkehrungen des * Headers umgangen, da es möglich ist, Credentials mitzugeben.

Fazit

Die Schwachstellen, welche durch CORS-Miskonfigurationen entstehen, können unter Umständen sehr gravierend sein. Die gute Nachricht ist, dass es sich bei diesen Fehlern um reine Miskonfigurationen handelt. Werden die CORS-Header richtig konfiguriert, können diese Schwachstellen somit behoben werden. Auch bei diesen Konfigurationen gilt der Grundsatz “Least Privilege”. Die CORS-Header sollten so weit wir möglich eingeschränkt werden und auf die benötigten Origins reduziert werden. Auch sollte die Implementation einer Whitelist-Überprüfung gründlich geprüft werden, um mögliche Fehlkonfigurationen zu verhindern. Letztendlich ist es essenziell, dass Entwickler verstehen wie CORS genau funktioniert und welche Gefahren eine Fehlkonfiguration mit sich bringt, damit sichergestellt werden kann, dass die Webapplikationen sowohl sicher als auch funktional bleiben und keine unerwarteten Zugriffsbeschränkungen oder Sicherheitslücken entstehen.

Informationen zum Autor: Annika Salzmann

Ein Programmierkurs hat bei mir als Kind die Leidenschaft für Computer und IT geweckt. Während meinem Informatikstudium habe ich dann meine Faszination für die IT-Security entdeckt. Seit Herbst 2022 darf ich nun bei der goSecurity, begleitend zu meinem Studium, meinem Wissensdrang nachkommen und meine Kenntnisse im Bereich Softwareentwicklung einbeziehen.