This follows up from my previous entry where I found a very easy way to set HttpOnly cookies for Internet Explorer 6 (SP1+) without requiring PHP 5.2 or hand-rolling set-cookie routines.
Due to reasons too long to explain here Firefox doesn’t have support for these cookies yet. However, there is a Firefox (well, Gecko) only solution.
The solution requires you to add this simple line of javascript as near to the top of your document as you can.
HTMLDocument.prototype.__defineGetter__("cookie",function (){return null;});
This overwrites the default cookie “getter” function and simply returns “null” when document.cookie is requested.
Unfortunately, it suffers from two major problems.
Firstly, it prevents any cookies from being read by javascript. This is a problem if you have javascript set and get cookies on the fly for dynamic web applications (read Web 2.0). and more fatally it can be undone simply by using:
delete HTMLDocument.prototype.cookie.
For example, if you managed to craft a link like so:
<a href='javascript:alert(document.cookie)'>Click me!</a>
Then you’d get an alert box with “null” thanks to the overridden cookie getter – however, crafting this link:
<a href='javascript:delete HTMLDocument.prototype.cookie; alert(document.cookie)'>Click me!</a>
Then you’d get access to the document.cookie as we’ve just deleted our custom getter.
The first problem is quite easy to overcome. You can set up an array of cookies that you want to ‘hide’ then parse up document.cookie storing the cookie values into an array and simply return the value of the array key when you would otherwise request a cookie.
The second problem is a little more difficult. After toying with it for a little while, I took a rather crude approach. I simply add an onload function that examines the document’s innerHTML and just replace “HTMLDocument.prototype” with a safer version (note, you can’t convert to hex or ascii entities as Firefox runs it just the same). There are two cavaets however.
Firstly, this will only stop “passive” attacks – that is scripting in URLs and images. It won’t stop a <script>..</script> block of code as the script code is executed before the onload function is called. Secondly, as the document’s innerHTML is replaced, all subsequent javascript on the page will stop working. I don’t consider that a major stumbling block as there aren’t many legitimate cases where you’d want someone to post “HTMLDocument.prototype”. If it is a problem, then I suggest you add a line of code in your PHP script to convert this string into something safer before javascript has a chance to see it.
Of course, this isn’t an excuse to stop checking for javascript in URLs and images – but it does provide another barrier a determined hacker must overcome before accessing your cookie information. It’s also not fool proof. There are hundreds of ways to obfuscate javascript code and it’s almost impossible to catch them all. Hopefully when Firefox 2 is out of BETA it’ll support HttpOnly cookies and deprecate this rather crude solution.
Here’s the complete code.
var COOKIES = new Array();
var uagent = navigator.userAgent.toLowerCase();
var is_moz = ( navigator.product == ‘Gecko’ );
var _tmp = document.cookie.split(‘;’);if ( _tmp.length )
{
for( i = 0 ; i < _tmp.length ; i++ )
{
var _data = _tmp[i].split(‘=’);
var _key = php_trim( _data[0] );
var _value = unescape( php_trim( _data[1] ) );
if ( _key && ( ! php_in_array( _key, ignore_cookies ) ) )
{
COOKIES[ _key ] = _value;
}
}
}
_tmp = null;
if ( is_moz )
{
HTMLDocument.prototype.__defineGetter__( “cookie”, function () { return null; } );
window.addEventListener( ‘load’, function() {
var _a = document.body;
var _x = _a.innerHTML;
var _y = new RegExp( “HTMLDocument\.prototype”, ‘ig’ );
if ( _x.match( _y ) )
{
_x = _x.replace( _y, ‘HTMLDocument_prototype’ );
_a.innerHTML = _x;
}
}, false );
}
/**
* Emulate PHPs trim function
*/
function php_trim( text )
{
text = text.replace(/^\s+/, ”);
return text.replace(/\s+$/, ”);
};
/**
* Emulate PHPs in_array function
*/
function php_in_array( needle, haystack )
{
if ( ! haystack.length )
{
return false;
}
for ( var i = 0 ; i < haystack.length ; i++)
{
if ( haystack[i] === needle )
{
return true;
}
}
return false;
};
function get_cookie( name )
{
return COOKIES[ name ];
}

{ 12 comments… read them below or add one }
Correct me if I’m wrong, but wouldn’t:
var foo=HTMLDocument.prototype;
delete foo.cookie;
bypass this.
Or also possibly:
delete (HTMLDocument.prototype).cookie
That’s a good point. I’ll ammend the code to check for HTMLDocument.prototype.
Aah, I see jelsoft releasing vb 3.6.1 on 13th sept with an implementation of HTTPOnly Cookie. lol :p
I’m sure they have him on their rss feed readers
oho “Jelsoft start your photocopiers!”
Haha, i so agree with that
We had HTTP Only cookies written before 3.6.0 was released, but as everyone knows untested code late in the release process is bad.
A patch was submitted to PHP on August 7th so that everyone could benefit from this.
http://news.php.net/php.internals/25233
So if anything Matt has blogged about a feature that Jelsoft helped to implement in PHP well over a month ago.
Hope that clears things up for all of you.
lol
Thanks for the heads up Scott. And thanks to Jelsoft for the patch (as well as for the update on vb 3.6!)
And Matt, well um..I guess thanks as well…
Won’t a simple eval get round this?
Here is another working hack. This time a client side protection.
http://blog.php-security.org/archives/40-httpOnly-Cookies-in-Firefox-2.0.html
Just found this post after searching the ‘net trying to find out why the Google Analytics code throws errors when used in IPB.
I understand being worried about cookies, but is this the right way to go about addressing these issues?
I’m sure I’m not the only one who has spent the better part of an hour trying to figure it out.
Perhaps fixing the back-end code to strip out unwanted input is a better idea?