dnnWerk.at - Michaels Blog

Sicherheit für DNN-Installationen Teil 5 - Content Security Policy

Sicherheit - 20.02.2018

Sicherheit DNN IIS HTTPS

Was ist eine Content Security Policy (CSP)

Eine CSP ist eine effektive und effiziente Maßnahme, die Sicherheit einer Website zu erhöhen, indem man Quellen für Inhalte wie Skripte, Stylesheets, Bilder usw. (mangels eines besseren deutschen Wortes nenne ich das hier einmal "Assets") in eine Whitelist einträgt und so maßgeblich verhindert, dass schädliche Inhalte aus anderen Quellen geladen werden können. Sie wird über einen Antwort-Header zur Verfügung gestellt und definiert zugelassene Quellen, aus welchen der Browser Inhalte beziehen darf.

Diese Seite (ja, diese spezielle Seite die du gerade liest) lädt z.B. Skripte von AddThis.com - die netten kleinen Sozial-Icons am rechten Bildrand, um die Seite auf Facebook oder Google+ oder anderen Sozialen Netzwerken zu teilen (darfst du, lieber Besucher gerne machen :-)). Und so nebenbei wird auch noch ein Zähler für den Seitenzugriff hochgesetzt, vollkommen anonym natürlich. Der Quellcode der Seite befiehlt dem Browser quasi, diese Scripts zu laden - aber woher soll der Browser wissen, ob das tatsächlich erwünscht ist oder nicht? Hat vielleicht jemand den Quellcode gehackt und verweist nun auf irgendwelche bösartigen Skripte oder manipulierte Bilder?

Durch die Angabe einer Whitelist kann man dem Browser mitteilen, welche Quellen man für vertrauenswürdig hält - und dann werden Seiten nicht mehr vollständig geladen, wenn angegebene Ressourcen nicht auf dieser Whitelist stehen.

Welche Werkzeuge benötigt man zum Erstellen einer CSP?

Nun ja - einen Browser mit einem vernünftigen Debugger. So sehr ich die Microsoft-Browser verwende (ja, doch, Edge ist gut, und diese Diskussion ersparen wir uns hier jetzt) - für diesen Zweck bevorzuge ich Chrome.

Superbequem sind die Werkzeuge, die man auf report-uri.com findet.

Und natürlich einen Text-Editor zum Bearbeiten der web.config. Notepad, wenns sein muss. Notepad++ wer bequem editieren will (oder was auch immer bevorzugt wird - nur ich würde kein Visual Studio auf dem Produktions-Server installieren.

Los geht's!

Wir haben bereits Teil 4 erste Vorbereitungen getroffen, indem wir dieseDirektive in die web.config eingefügt haben:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Damit haben wir die Voraussetzung geschaffen, eine CSP zu erstellen. Der Sinn der Sache ist ganz einfach: Im Debugger von Chrome wird uns damit angezeigt, was alles blockiert worden wäre, wenn die CSP scharf geschaltet wäre.

Content Security Policy (CSP) - Report-Only

Das sieht im ersten Moment ganz schön heftig aus, und es ist auch eine Menge Arbeit. Vor allem aber: Die Arbeit endet nicht. Jede Änderung an der Seite, jede neue Bibliothek, die wir einbinden - in Zukunft muss alles regelmäßig kontrolliert werden. Und vor allem: Bei der Ersterstellung einer CSP müssen wir alle (ja, alle) Seiten der Website aufrufen und testen, und nicht nur die Startseite. Aber keine Sorge: Ist die Startseite einmal erledigt, wird auf den anderen Seiten nicht mehr so viel los sein. :-)

ACHTUNG: Die hier erstellte CSP ist beispielhaft - also nicht abschreiben, sondern den Gegebenheiten der jeweiligen Website anpassen!

Skripts

Nehmen wir hier einmal den ersten Eintrag:

[Report Only] Refused to execute inline script because it
violates the following Content Security Policy directive: "default-src
'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-
KOxdWeOraXVLEswTPIl7wvBXZ1ZOatgrBKLNZZbnSSk='), or a nonce ('nonce-…')
is required to enable inline execution. Note also that 'script-src' was
not explicitly set, so 'default-src' is used as a fallback.

Interpretieren wir das: Wir haben in unserer Direktive oben angegeben, dass die Quelle für beliebige Inhalte Assets "self", also der Web-Server selbst ist. Das heißt, dass ein Asset z.B. mit der Direktive

<script src="/js/MeinTolles.js" type="text/javascript"></script>

eingebunden werden darf. Ein Skript, welches aber im Klartext im HTML-Code steht darf mit dieser Policy nicht geladen werden. Es gibt drei Ansätze, das Problem zu lösen:

  • Entweder man erlaubt alle Inline-Skripts im Klartext - das hat den Vorteil, dass man wenig Arbeit hat, man schaltet damit aber manipulierten Seiten den Weg frei.
  • Man hasht das Skript. Das ist leider bei ASP.Net-Seiten nicht so ohne weiteres möglich, und im Fall von DNN-Seiten müsste das eigentlich im Core geschehen, was nicht der Fall ist.
  • Man verwendet ein Nonce. Leider ist auch das nicht immer möglich, da wir keinen keinen Einfluss auf die Generierung des Quellcodes haben.

Auf jeder Webforms-Seite findet man beispielsweise folgendes Skript:

<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['Form'];
if (!theForm) {
    theForm = document.Form;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
//]]>
</script>

Skript im Klartext, kein Nonce angegeben - Pech. Leider können wir hier tatsächlich nur auf Variante 1 zurückgreifen. Unsere CSP wird also einmal wie folgt abgeändert:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline';
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Damit erlauben wir für Skripts einmal diejenigen Quellen, die auf dem Server selbst liegen, und die im Code im Klartext vorkommen. Doch damit ist noch immer zuviel blockiert. Sehen wir uns die nächste Fehlermeldung an:

[Report Only] Refused to load the script 'https://ssl.google-analytics.com/ga.js' because it
violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline'".

Hier wird ein Skript aus einem sogenannten Content Delivery Network (CDN) geladen. Das hat mehrere Vorteile, u.a. verbesserte Geschwindigkeit und aktuellste Versionen. Natürlich gibt es auch Nachteile, wenn z.B. das CDN nicht verfügbar ist können die externen Inhalte nicht geladen werden.

Um nun dem Browser zu erlauben, dass Skripts von bestimmten CDNs, die unsere Site aufruft, verwendet werden dürfen, müssen wir diese in der CSP anführen:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Formatvorlagen (Style Sheets)

Das gleiche gilt auch für Style-Sheets. Es kommt natürlich immer wieder vor, dass man seltene Formatvorlagen schnell einmal mit Hilfe des Attributs style="…" erstellt und nicht eine Klasse in einer (externen) CSS-Datei dafür baut.

[Report Only] Refused to apply inline style because it violates the following Content
Security Policy directive: "default-src 'self'". Either the 'unsafe-inline' keyword,
a hash ('sha256-B91WCBkQxKx4QoVssWt2NHfyzEEjcJkBtWYyLqTIgvE='), or a nonce ('nonce-…')
is required to enable inline execution. Note also that 'style-src' was not explicitly
set, so 'default-src' is used as a fallback.

Daher müssen wir in diesem Fall auch die Option unsafe-inline für Formatvorlagen angeben:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   style-src 'self' 'unsafe-inline';
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Bilder

[Report Only] Refused to load the image 'https://ssl.google-analytics.com/r/__utm.gif?[…]'
because it violates the following Content Security Policy directive: "default-src 'self'".
Note that 'img-src' was not explicitly set, so 'default-src' is used as a fallback.

Auch das muss in der CSP angegeben werden. Für DNN-Installationen ist hier auch die Url update.dotnetnuke.com wichtig, das ermöglicht die Anzeige des "Critical Update"-Logos (wenn ein solches vorhanden ist und man als Systemverwalter angemeldet ist) bzw. der Hinweise auf neuere Versionen von Erweiterungen auf der entsprechenden Seite.

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   style-src 'self' 'unsafe-inline';
   img-src 'self' ssl.google-analytics.com update.dotnetnuke.com;
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Bilder können aber auch errechnet werden, was Fehlermeldungen wie die folgende verursachen kann:

[Report Only] Refused to load the image 'data:image/png;base64,
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAMAAAAoyzS7AAAAGXRFWHRTb2Z0d2FyZQBBZ…

In diesem Fall gibt man data: als Bildquelle an:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   style-src 'self' 'unsafe-inline';
   img-src 'self' ssl.google-analytics.com update.dotnetnuke.com data:;
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

Schriftarten

[Report Only] Refused to load the font 'https://fonts.gstatic.com/s/opensans/v15/mem8YaGs126MiZpBA-UFWJ0bbck.woff2'
because it violates the following Content Security Policy directive: "default-src 'self'".
Note that 'font-src' was not explicitly set, so 'default-src' is used as a fallback.

Lösung:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self';
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   style-src 'self' 'unsafe-inline';
   img-src 'self' ssl.google-analytics.com update.dotnetnuke.com;
   font-src: 'self' fonts.gstatic.com;
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />
Kleiner Hinweis: woff2 ist eventuell nicht als MIME-Type definiert, in diesem Fall muss man das auch dort erledigen damit die Schriftart geladen werden kann.

Umgang mit unsicheren Anfragen

Eigentlich wollen wir ja nun, dass der Browser Anfragen im sicheren HTTPS-Protokoll erhält und beantwortet, und zwar bevor die Anfrage überhaupt gestellt wird. Einige Browser unterstützen dieses Ansinnen bereits, indem sie die CSP cachen (naja, etwas laienhaft ausgedrückt, aber bleiben wir dabei). Drei Direktiven helfen uns dabei:

<add name="Content-Security-Policy-Report-Only" value="default-src 'self' https:;
   script-src 'self' 'unsafe-inline' ssl.google-analytics.com;
   style-src 'self' 'unsafe-inline';
   img-src 'self' ssl.google-analytics.com update.dotnetnuke.com data:;
   upgrade-insecure-requests;
   block-all-mixed-content;
   form-action https;
   report-uri https://meinesubdomainbei.report-uri.com/r/d/csp/reportOnly;" />

In der ersten Zeile weisen wir den Browser an, jedes Asset ausschließlich über https zu laden. Das kann Folgen haben. Stellen wir uns einmal vor, ein Bild wird über folgenden HTML-Code eingebunden:

<img src="http://www.meineseite.com/images/bild_1.jpg">

Wenn man nun den Browser anweist, nur sichere Inhalte zu laden (und das haben wir hier mit der Änderung der ersten Zeile getan), dann wird dieses Bild nicht geladen. Wenn wir diese direktive nicht setzen, dann warnt der Browser, dass hier gemischte Inhalte sind (so, wie das passieren würde, wenn wir Fremdinhalte über http und nicht über https in unsere Seite einfließen lassen, z.B. über einen IFrame), ließe uns aber die Möglichkeit, auch die unsicheren Inhalte zu laden.

Müssen wir nun also unseren gesamten Content darauf überprüfen und ändern? Und was passiert, wenn irgendwo in DNN jemand "gepatzt" hat und ein Javascript oder ein Bild hart über http eingebunden hat? Darauf haben wir ja gar keinen (oder nur wenig) Einfluss.

Gottseidank ist das nicht so. Die zweite neu hinzugefügte Anweisung (upgrade-insecure-requests) sorgt dafür, dass der Browser alle Inhalte dieser Site über https lädt - auch, wenn explizit irgendwo http stehen sollte.

Die dritte dieser Anweisungen (block-all-mixed-content) steht damit in engem Zusammenhang: Der Browser wird angewiesen, keine Inhalte über http zu laden. Man muss sich des Unterschieds bewusst sein, und diese Anweisung kann auch dazu führen, dass externe Inhalte, die über http geladen werden, dann nicht mehr angezeigt werden. Auch hier gilt: Implementieren, was notwenig ist!

Die letzte dieser Anweisungen sorgt dann noch dafür, dass Formulare ausschließlich über https übermittelt werden, auch wenn jemand hart <form action="http://… kodiert haben sollte.

Fazit

Ich hoffe, dass ich hier eine Idee davon geben konnte, wie man eine effiziente CSP erstellt. Aber eines muss uns dabei allen klar sein: Dies ist nicht mit der Erstellung abgeschlossen. Es ist vielmehr ein laufender Prozess, nach jedem Update von DNN oder einzelner Module, nach jedem Einfügen von externen Javascript-Libraries oder Stylesheets über CDNs usw. muss die CSP überprüft und angepasst werden - sonst funktioniert irgendwann einmal gar nichts mehr.

Weiter: Sicherheit für DNN-Installationen Teil 6 - Der Qualys-Test