Suche
Webentwicklung mit Angular

Angular Sicherheit: CSRF-Schutz am Beispiel Spring Boot

Karsten Sitterberg

(c) Shutterstock / Profit_Image

 

In dieser Folge der Kolumne „Webentwicklung mit Angular“ geht Karsten Sitterberg auf eine der häufigsten Sicherheitsprobleme in Webanwendungen ein: Cross-Site-Request-Forgery. Anhand eines Spring-Boot-Beispiels erklärt er, wie man die CSRF-Lücke in Angular-Anwendungen schließt.

CSRF-Schutz am Beispiel Spring Boot

Cross-Site-Request-Forgery, oder CSRF, gehört zu den ewigen Einträgen der Top-10-Charts von Sicherheitslücken bei Webanwendungen. Der Hintergrund ist schnell erklärt: Die Architektur des Web basiert darauf, dass Webseiten Inhalte von unterschiedlichen Quellen einbinden können. So kann beispielsweise ein Artikel zum Thema “Katzen” das Bild einer Wildkatze von Wikipedia enthalten und eine JavaScript-Bibliothek von Google einbinden. Der Browser sendet dazu, ohne dass es wie bei einem Link einer Interaktion mit dem Benutzer bedarf, einen GET-Request an die betreffenden Ressourcen.

Auch wenn gemäß Spezifikation ein GET-Request keine Veränderungen auslösen darf, gibt es dennoch einige Systeme, die sich nicht an diese Vorgabe halten. So erlauben beispielsweise einige Router die Änderung des DNS-Servers über einen GET-Request. Damit reicht das einfache Einbetten eines passenden URLs als Bild auf einer böswilligen Seite, und angelockte Besucher lösen mit ihrem Browser entsprechende Requests aus. Dazu bedarf es nicht einmal JavaScript.

Spring-Boot-Beispiel

Wir betrachten das folgende Beispiel:

<img src=“http://192.168.0.1/newDns?server=10.6.6.6″/>

Verändernde Zugriffe sollen mit der POST-Methode umgesetzt werden, doch auch damit ist man nicht sicher vor böswilligen Webseiten, welche entsprechende Requests auslösen. Mit JavaScript können solche Requests ganz versteckt und automatisch geschehen. Bringt man den Nutzer dazu, einen Knopf zu drücken, so kann dies auch ohne JavaScript umgesetzt werden.

Es gibt viele, die denken „aber ohne Anmeldung kann niemand auf die Funktionen zugreifen“ und sich so in trügerischer Sicherheit wiegen. Doch funktionieren viele Anmeldeverfahren mit Cookies, worüber verschiedene Anfragen derselben Sitzung und dem entsprechenden Nutzer zugeordnet werden können: Da HTTP zustandslos ist, wird ein Korrelationsmerkmal benötigt, um eine Zuordnung zu ermöglichen. Die einfachste und komfortabelste Form dazu bilden eben Cookies ab. Einmal vom Server gesetzt, werden sie vom Browser automatisch verwaltet und bei weiteren Requests an den Server zurückgeschickt. Und das bei allen Requests. Natürlich kann eine böswillige Website die Cookies nicht auslesen. Doch das ist bei CSRF-Angriffen auch nicht das Ziel. Das Ziel ist, dass man einen Browser dazu bekommt, per Fernsteuerung Aktionen auszulösen, die nicht vom Nutzer intendiert sind.

Der Grundgedanke, um CSRF-Angriffe zu verhindern, geht von den Eigenschaften der Browser-Sandbox aus: Einfache Zugriffe, z.B. einfache GET- oder POST-Requests, sind generell erlaubt, auch zwischen verschiedenen Domains. Die Inhalte können jedoch nicht gelesen werden, sie werden von der Browser-Sandbox verhindert. Um CSRF-Angriffe zu unterbinden, macht man sich genau diesen Unterschied zunutze: Der Absender eines Requests muss dem Server beweisen, dass er lesenden Zugriff auf die Antworten des Servers hat und daher aus dem Kontext der Anwendung stammt. Im Folgenden werden einige Szenarien aufgeführt, mit denen der Server diesen lesenden Zugriff überprüfen kann.

Auch interessant: Angular 2.0 – Migrationspfade von 1.x zu 2.0

Bei klassischen Webanwendungen liefert der Server HTML aus. Darin eingebettet sind Formulare, die ein mit einem CSRF-Token vorausgefülltes, verstecktes Feld beinhalten. Böswillige Webseiten können diese Token dank der Browser-Sandbox nicht auslesen. Hierin liegt auch der Schlüssel zur Lösung für CSRF-Problematiken in Angular-Anwendungen: Um anzuzeigen, dass es sich um eine gewollte Interaktion der Anwendung handelt, kann man einen speziellen Header setzen, in dem das CSRF-Token enthalten ist. Der Server muss dann lediglich den Wert des Tokens mit dem von ihm vorher übermittelten Wert vergleichen, um festzustellen, ob es sich um einen legitimen Request handelt.

Beruht das Authentifizierungsverfahren nicht auf Sessioncookies, sondern beispielsweise auf der IP-Adresse oder Client-Zertifikaten, möchte man aus Architekturgesichtspunkten Zustand vermeiden, der den Client an eine spezifische Serverinstanz bindet oder erfordert, dass eine Synchronisierung zwischen den Anwendungs-Servern stattfindet. Also muss der Inhalt des CSRF-Tokens verfügbar sein, ohne einen Shared-State zu erhalten. Die Lösung dafür liefern wieder Cookies: Der Server kann einen Cookie mit dem CSRF-Token beim Client setzen. Der Client liest den Cookie aus und setzt einen speziellen Header, der Cookie wird weiterhin automatisch an den Server geschickt. Auf Serverseite wird dann der Header mit dem Cookie-Wert verglichen, womit ausgeschlossen wird, dass es sich um eine Anfrage handelt, die von Dritten ausgelöst wurde.

Angular hat dafür bereits einen Standardmechanismus: Wird ein Cookie mit dem Namen XSRF-TOKEN gefunden, so wird der Wert ausgelesen und in den Header X-XSRF-TOKEN gesetzt. Falls ein Cookie mit anderem Namen eingesetzt wird, so kann dies natürlich konfiguriert werden:

@NgModule({
 \\...
 providers: [
   {
     provide: XSRFStrategy,
     useValue: new CookieXSRFStrategy('myCookieName', 'My-Header-Name')
   }
 ]
})
export class AppModule{}

 

Auf Backendseite muss lediglich der Cookie, z.B. als Teil des Login, gesetzt werden. Bei jedem Request muss dann eine entsprechende Überprüfung stattfinden.

Das kann in Spring Boot folgendermaßen konfiguriert werden:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository
.withHttpOnlyFalse());
}
}

 

Das CookieCsrfTokenRepository ist dabei ursprünglich extra für AngularJS entwickelt worden, wie man der Dokumentation entnehmen kann:

A CsrfTokenRepository that persist the CSRF token in a cookie named

„XSRF-TOKEN“ and reads from the header „X-XSRF-TOKEN“ following the conventions of AngularJS. When using with AngularJS be sure to use withHttpOnlyFalse().

Verwendet man ein Authentifizierungsverfahren wie OpenID Connect oder OAuth2, bei dem ein Authorization-Bearer-Header gesetzt wird, so kann man auf den CSRF Header im Prinzip verzichten: Durch den zur Authentifizierung eingesetzten Custom Header wird auch Cross-Site-Request-Forgery verhindert. Außerdem findet kein Log-in an der selben Anwendung mehr statt, sodass ein gesonderter Weg beschritten werden müsste, um einen entsprechenden Cookie mit CSRF-Token zu erhalten, was die Komplexität bei der Anbindung von Clients erhöht.

Ausblick

Mit XSS (Cross-Site-Scripting) kann man den Lesevorgang per JS ermöglichen und sich dadurch den Inhalt von CSRF-Token verschaffen, womit man dann beliebige Requests auslösen kann. Angular verfügt über einige Vorkehrungen, um vor XSS zu schützen. Allerdings ist es dennoch erforderlich, auch XSS als Teil der Sicherheitsarchitektur zu betrachten.

Geschrieben von
Karsten Sitterberg
Karsten Sitterberg
Karsten Sitterberg ist als freiberuflicher Entwickler, Trainer und Berater für Java und Webtechnologien tätig. Karsten ist Physiker (MSc) und Oracle-zertifizierter Java Developer. Seit 2012 arbeitet er mit trion zusammen.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.