JAX Blog

Sichere Web-Apps für alle

Mit Content Security Policy gegen Cross-site Scripting

Jan 5, 2024

Das Einschleusen von Schadcode über eine fremde Domäne lässt sich mit einer Content Security Policy (CSP) erheblich einschränken. Erfahren Sie, wie Sie Ihre Webapplikation gegen Cross-site-Scripting-(XSS-)Attacken härten können.

Formulare in Webapplikationen sind für so manche Hackerattacke ein geeignetes Einfallstor. Ganz gleich, ob es ein Angriff auf die Datenbank mit einer SQL Injection ist, oder ob schädliches JavaScript per XSS [1] nachgeladen und zur Ausführung gebracht wird.

Gefahr durch Eingaben

Sicherlich ist der erste und wichtigste Schritt, sämtliche Variablen, die durch Formularfelder befüllt werden, auf eine gültige Eingabe hin zu prüfen. Ein gängiges Mittel ist die Validierung über reguläre Ausdrücke, die sicherstellen, dass nur gültige Zeichen und ein korrektes Format zugelassen werden. Möchte man aber beispielsweise formatierte und umfangreiche Artikel ermöglichen, wie es bei einem Content-Management-System der Fall ist, haben die gängigen Schutzmaßnahmen durchaus ihre Schwächen. Denn das Prinzip, möglichst die Eingabe zu beschränken und gefährliche Zeichen herauszufiltern, hat bei formatiertem Text seine Grenzen.

Wollen wir beispielsweise ein Zitat in Anführungszeichen setzen, können wir die beiden Zeichen ‘ und “ nicht einfach aus dem Text entfernen, da diese ja Bestandteil des Inhalts sind. In der Vergangenheit nutzte man bei dieser Problematik einen zusätzlichen Mechanismus, um Schutz vor Cyberangriffen sicherzustellen. Hierfür werden sämtliche kritische Zeichen wie spitze Klammern, Anführungszeichen und Backslashes über ihre HTML Escapes ersetzt. Damit gelten diese als problematisch klassifizierten Sonderzeichen nicht mehr als Quellcode, da aus > ein &gr; wird. Daher sind die soeben beschriebenen Maßnahmen besonders wirkmächtig und bieten bereits einen effektiven Schutz.

Die Schutzwirkung lässt sich allerdings über den Mechanismus CSP erheblich verbessern, da dieser weit über das Prüfen von Nutzereingaben hinausgeht. Sämtliche in einer Webapplikation geladene Ressourcen wie Bilder, CSS und JavaScript lassen sich dank dieser Technologie steuern.

Die Content Security Policy ist ein Projekt des W3C und greift als Bestandteil des Browsers – es sind keine zusätzlichen Plug-ins oder Ähnliches zu installieren. Für aktuelle Browser gilt CSP Level 3. Auf der entsprechenden Webseite des W3C [2] finden Sie eine vollständige Übersicht aller Versionen einschließlich der unterstützenden Webbrowser.

In Umgebungen, die erhöhte Sicherheitsanforderungen erfordern, beispielsweise Onlinebanking, kann die Browserversion des Clients abgefragt werden und bei erheblich veralteten Varianten des Browsers somit der Zugriff verweigert werden.

String userAgent = request.getHeader("user-agent");
String browserName = "";
String  browserVer = "";
if(userAgent.contains("Chrome")){
  String substring=userAgent.substring(userAgent.indexOf("Chrome")).split(" ")[0];
  browserName=substring.split("/")[0];
  browserVer=substring.split("/")[1];
} else if(userAgent.contains("Firefox")) {
  String substring=userAgent.substring(userAgent.indexOf("Firefox")).split(" ")[0];
  browserName=substring.split("/")[0];
  browserVer=substring.split("/")[1];
}

Stay tuned

Regelmäßig News zur Konferenz und der Java-Community erhalten

 

Listing 1 zeigt die Möglichkeit, über den request die Identifikation des Browsers auszulesen. Anschließend demonstrieren die beiden Beispiele, wie die Versionsnummer aus dem Text für Chrome und Firefox extrahiert wird. Bei dieser Strategie sollte man sich aber bewusst sein, dass es für Schwindler recht einfache Möglichkeiten gibt, die Nutzung eines anderen Browsers vorzugaukeln.

Damit wird uns noch einmal vor Augen geführt, dass Web Application Security als ganzheitliches Konzept zu verstehen ist, in dem mehrere Maßnahmen als Verbund wirken – denn einzelne Techniken lassen sich durchaus umgehen. Schauen wir uns daher nun den Wirkmechanismus von CSP im Detail an.

Beginnen wir mit einer reinen HTML-Ausgabe, die CSS, Google Fonts, CDN JavaScript, ein YouTube iFrame und Grafiken enthält. Listing 2 dient uns als Ausgangspunkt, ohne den Einsatz von CSP.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    
    <title>Content Security Policy Demo</title>
    
    <link rel="stylesheet" href="style.css" />
    
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
      rel="stylesheet" />
    
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
      integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2"
      crossorigin="anonymous" />
  </head>

  <body>
    <main>
      <div class="box">
        <div id="vue"></div>
      </div>

      <div class="box">
        <div class="embed">
          <iframe width="100%" height="500px" frameborder="0"
            src="https://www.youtube.com/embed/3nK6rcAbuzo"
            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
            allowfullscreen></iframe>
        </div>
      </div>

      <div class="box">
        <div class="grid">
          <img src="https://images.unsplash.com/photo-1535089780340-34cc939f9996?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80" />
          <img src="https://images.unsplash.com/photo-1587081917197-95be3a2d2103?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=80" />
          <img src="https://images.unsplash.com/photo-1502248103506-76afc15f5c45?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=80" />
        </div>
      </div>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
    <script nonce="rAnd0m">
      new Vue({
        el: '#vue',
        render(createElement) {
          return createElement('h1', 'Hello World!');
        },
      });
    </script>
  </body>
</html>

Abb. 1: HTML ohne CSP

Wie wir in Abbildung 1 sehen können, handelt es sich hierbei um eine einfache HTML-Seite, die verschiedenen Content lädt. Dazu gehört zum Beispiel auch, das JavaScript-Framework Bootstrap extern über das Content Delivery Network (CDN) jsDelivr zu laden. Wer mit dem Thema CDN noch nicht vertraut ist, findet dazu einen sehr informativen Artikel auf Wikipedia [3]. Stellen wir uns nun einmal ein realistisches Szenario aus der Praxis vor.

Es könnte nun sein, dass die von uns verwendete Bootstrap-Bibliothek eine bekannte Sicherheitslücke für XSS-Angriffe enthält. Der Content des CDN von jsDelivr ist durchaus vertrauenswürdig. Ein Angreifer könnte nun aber versuchen, von einem Webspace eine eigens präparierte JavaScript-Datei über die Bootstrap-Sicherheitslücke einzuschleusen. Ich möchte an dieser Stelle natürlich kein Hackertutorial an die Hand geben und erklären, wie ein solcher Angriff ausgeführt werden kann; ebensowenig möchte ich Ideen verbreiten, welcher Unfug sich über XSS anstellen lässt. Mir geht es darum, eine zuverlässige Methode vorzustellen, die den Angriff wirkungsvoll verhindern kann. Also richten wir unser Augenmerk nun auf die Prävention von XSS.

Schutz durch CSP

Mittels CSP werden nun Meta-Header-Attribute gesetzt, mit denen bestimmt werden kann, was zuverlässige Quellen sind. Das erlaubt es jedem Contenttyp (Ressource, also Bild, CSS und so weiter), explizite, vertrauenswürdige Quellen (Domains) zuzuweisen. Damit lassen sich JavaScript-Dateien so einschränken, dass sie nur vom eigenen Server geladen werden und beispielsweise von CDN jsDelivr. Findet ein Angreifer nun einen Weg, eine XSS-Attacke auszuführen, bei der er versucht, von einer anderen Domain als von den erlaubten Domains Schadcode einzuschleusen, blockiert CSP das Skript von der nicht freigegebenen Domain.

Dazu ein einfaches Beispiel: Wenn wir im <head> in Listing 2 gleich nach dem meta-Tag für die Seitencodierung die Zeile <meta http-equiv=”Content-Security-Policy” content=”default-src ‘self'” /> einfügen und die Datei ausführen, bekommen wir eine Ausgabe wie in Abbildung 2.

Abb. 2: Blocked Content

Der Teil content=”default-src ‘self'” sorgt dafür, dass sämtliche Inhalte, die nicht von der eigenen Domäne stammen, über CSP im Browser blockiert werden. Wollen wir zusätzlich für JavaScript das jsDelivr CDN zulassen, müssen wir den meta-Tag wie in folgt formulieren:

<meta http-equiv="Content-Security-Policy"
  content="default-src 'self';
  script-src 'nonce-rAnd0m' https://cdn.jsdelivr.net; />

Auf der offiziellen Webseite für Content Security Policy [4] finden Sie eine vollständige Auflistung sämtlicher Attribute und eine umfangreiche Liste an Beispielen. So bewirkt der Ausdruck nonce-rAnd0m dass Inline-Skripte geladen werden dürfen. Dazu muss das zugehörige JavaScript mit dem Attribut [script nonce=”rAnd0m”/] gekennzeichnet werden. Damit unser Beispiel aus Listing 2 vollständig funktioniert, benötigen wir für den CSP-meta-Tag folgenden Eintrag (Listing 3).

<meta http-equiv="Content-Security-Policy"
  content="default-src 'self';
  script-src 'nonce-rAnd0m' https://cdn.jsdelivr.net;
  img-src https://images.unsplash.com;
  style-src 'self' https://cdn.jsdelivr.net;
  frame-src https://www.youtube.com;
  font-src https://fonts.googleapis.com;" />

Für Java-Webapplikationen können die Headerinformationen zu den Beschränkungen der CSP über den response hinzugefügt werden. Die Zeile response.addHeader (“Content-Security-Policy”, “default-src ‘self'”); bewirkt wie zu Beginn, dass sämtliche Inhalte, die nicht von der eigene Domäne stammen, blockiert werden.

MEHR PERFORMANCE GEFÄLLIG?

Performance & Security-Track entdecken

 

Zentrale Regelung über Servlet-Filter

Nun ist das applikationsweite manuelle Hinzufügen von CSP-Regeln für jede eigene Seite recht mühselig und zudem noch sehr fehleranfällig. Eine zentrale Lösung für den Einsatz von Java-Servern ist die Verwendung von Servlet-Filtern (Listing 4).

public class CSPFilter implements Filter {

  public static final String POLICY = "default-src 'self'";

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  throws IOException, ServletException {
    if (response instanceof HttpServletResponse) {
      ((HttpServletResponse)response).setHeader("Content-Security-Policy", CSPFilter.POLICY);
    }
  }

  @Override
  public void init(FilterConfig filterConfig) throws ServletException { }

  @Override
  public void destroy() { }
}

Um den Filter dann zum Beispiel im Apache Tomcat zu aktivieren, wird noch ein kleiner Eintrag in der web.xml benötigt (Listing 5).

<filter>
  <filter-name>CSPFilter</filter-name>
  <filter-class>com.content-security-policy.filters.CSPFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>CSPFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

Sicher könnte man für sämtliche Applikationen in einem vorgeschalteten Proxyserver wie dem Apache-2-HTTP-Server oder NGNIX alle CSP-Regeln zentral verwalten. Davon ist allerdings aus zwei Gründen abzuraten: Zum einen verlagert sich so die Verantwortlichkeit von der Entwicklung hin zum Betrieb, was zu erhöhtem Kommunikationsaufwand und einem Flaschenhals in der Entwicklung führt. Zum anderen werden die so entstehenden Regeln sehr komplex und entsprechend schwieriger zu lesen beziehungsweise zu warten. Der hier vorgeschlagene Servlet-Filter ist daher eine gute Option, um die Sicherheitsregeln applikationsweit zentral für exakt die entwickelte Anwendung zu verwalten. Zudem passt das auch hervorragend in ein agiles DevOps-Konzept, denn die notwendige Konfiguration ist Bestandteil des Source Codes und über die Versionsverwaltung unter Konfigurationsmanagement zugänglich.

 

Fazit

Wie ich in diesem Artikel zeigen konnte, ist das Thema Web-Application-Security nicht immer gleich ein Fall für Spezialisten. Persönlich empfinde ich es als eine große Errungenschaft der letzten Jahre, wie das Härten von Webanwendungen kontinuierlich einfacher wird. CSP halte ich auf diesem Weg für einen Schritt in die richtige Richtung. Natürlich gilt es weiterhin, stets ein Auge auf aktuelle Entwicklungen zu haben, denn die bösen Buben und Mädels ruhen sich nicht auf ihren Lorbeeren aus und lassen sich ständig neue Gemeinheiten einfallen. Der gerade populär gewordene breite Einsatz von künstlichen neuronalen Netzen wie ChatGPT lässt im Moment nur grob erahnen, was uns künftig noch an Cyberangriffen aus den Tiefen des weltweiten Netzes erwarten wird.


Links & Literatur

[1] https://owasp.org/www-community/attacks/xss/

[2] https://www.w3.org/TR/CSP/

[3] https://de.wikipedia.org/wiki/Content_Delivery_Network

[4] https://content-security-policy.com

Alle News der Java-Welt:

Behind the Tracks

Agile, People & Culture
Teamwork & Methoden

Clouds & Kubernetes
Alles rund um Cloud

Core Java & Languages
Ausblicke & Best Practices

Data & Machine Learning
Speicherung, Processing & mehr

DevOps & CI/CD
Deployment, Docker & mehr

Microservices
Strukturen & Frameworks

Performance & Security
Sichere Webanwendungen

Serverside Java
Spring, JDK & mehr

Software-Architektur
Best Practices

Web & JavaScript
JS & Webtechnologien

Digital Transformation & Innovation
Technologien & Vorgehensweisen

Domain-driven Design
Grundlagen und Ausblick

Spring Ecosystem
Wissen in Spring-Technologien

Web-APIs
API-Technologie, Design und Management