sábado, 7 de septiembre de 2013

SE VA ACABANDO EL SOPORTE PARA WINDOWS XP (2 de 6)

LA CRITICIDAD DE LO IMPORTANTE

********************
Índice
 Parte 1
 Parte 2
 Parte 3
 Parte 4
 Parte 5
 Parte 6
********************


Nos quedamos en el post anterior preguntándonos sobre qué significa "Crítica" e "Importante". Otro documento de Microsoft nos lo aclara (http://technet.microsoft.com/es-es/security/bulletin/rating):
------------------
* Crítica: Vulnerabilidad que puede permitir la propagación de un gusano de Internet sin la acción del usuario.
* Importante: Vulnerabilidad que puede poner en peligro la confidencialidad, integridad o disponibilidad de los datos de los usuarios, o bien, la integridad o disponibilidad de los recursos de procesamiento.
* Moderada: El abuso podría reducirse en gran medida mediante factores como una configuración predeterminada, auditoría o dificultad de abuso.
* Baja: Vulnerabilidad muy difícil de aprovechar o cuyo impacto es mínimo.
------------------

Y aquí me planteo dos problemas.

El primero es que estas caracterizaciones las hace el MSRC. En este centro hay muy buenos profesionales pero, aún así, sus decisiones parten desde una perspectiva particular, propia. Y cada organización, a veces cada persona, es un mundo. Pensemos por ejemplo en una que permita una "denegación de servicio" de sus servidores web. Esa vulnerabilidad puede ser crítica, muy crítica, para una organización que base su modelo de negocio en la venta a través de Internet. Pero para una empresa que sólo utilice su presencia en la Red de Redes para hacerse publicidad el impacto sería, quizá, sólo moderado.

Y el segundo es que las vulnerabilidades no viven solas. Un mismo equipo puede tener(y de hecho casi seguro que tiene) un buen número de vulnerabilidades, muchas de ellas aún por detectar. ¿Qué puede ocurrir si se combinan dos o más en un ataque?

Veamos un ejemplo.

El boletín de seguridad MS13-053 (http://technet.microsoft.com/es-es/security/bulletin/ms13-053) describe una vulnerabilidad CRITICA de los controladores en modo kernel de Windows em los siguientes términos:
------------------
La vulnerabilidad más grave podría permitir la ejecución remota de código si un usuario consulta contenido compartido que inserta archivos de fuente TrueType. Un atacante que aprovechara esta vulnerabilidad podría lograr el control completo de un sistema afectado.
------------------

O sea, que alguien pueda ejecutar código en tu máquina y controlarla completamente de forma remota es crítico. En eso podemos estar de acuerdo.

Y vayan a continuación un par de ejemplos de vulnerabilidades que sólo son IMPORTANTES.

La primera está explicada en MS12-048 (http://technet.microsoft.com/es-es/security/bulletin/ms12-048):
------------------
La vulnerabilidad podría permitir la ejecución remota de código si un usuario abre un archivo o un directorio con un nombre especialmente diseñado. Un atacante que aprovechara esta vulnerabilidad podría conseguir el mismo nivel de derechos de usuario que el usuario actual.
------------------

Esta vulnerabilidad nos da una medida. El atacante puede ejecutar código, igual que antes, pero en este caso sólo conseguirá tener los permisos del usuario al que consiga comprometer. Eso es menos grave que si se consigue un control total del sistema, como en el caso anterior,... excepto si el usuario tiene permisos y privilegios que le otorgan un control total del sistema, claro.

Pero además, es que existen las vulnerabilidades de "escalada de privilegios". Como la descrita en MS13-063 (http://technet.microsoft.com/es-es/security/bulletin/ms13-063), que también está etiquetada como "IMPORTANTE".

O sea, que si un equipo tiene estas dos vulnerabilidades "importantes", quizá un atacante se las ingenie para aprovechar la primera para ejecutar código y la otra para conseguir elevar sus privilegios y ejecutar código con una cuenta más "poderosa". Una con permisos y privilegios que le otorguen un control total, o casi total, sobre la máquina.

Resumiendo: dos vulnerabilidades "importantes" combinadas pueden llegar a tener el mismo impacto que una "crítica". Y combinar vulnerabilidades es algo común en estos negocios...
(continuará...)

jueves, 18 de abril de 2013

El router de mi amigo (4 de 4)

La infraestructura

¡Hora de probar!

Me hice con un espacio web en cierto “hosting gratuito” que en adelante, y a efectos meramente didácticos, llamaré “malicioso.example.com”. Y en él creé tres ficheros

Fichero 1: El Javascript

El primero de los ficheros se llamaba “a”. Nombre corto, pero suficiente. Al igual que el código que contenía:



Ése era el script que quería ejecutar aprovechando la vulnerabilidad XSS. En pocas palabras, carga la página de cambio de contraseña y busca la línea que contiene “var pwdUser”, que es donde aparece el valor de la contraseña. Una vez la encuentra, la manda al servidor malicioso.example.com, al puerto 9999, donde un proceso está a la espera de recibir noticias.

Fichero 2: El formulario fantasma

Ahora necesitaba que la víctima sufriera las consecuencias del ataque XSS. Para ello me aproveché la vulnerabilidad CSRF, creando una página que simulara el formulario de configuración de impresora del router, pero sin tantas comprobaciones tontas.

Y, de camino, metiendo en el campo de la marca y el modelo el código que disparaba el XSS.
Una vez dispuesto el formulario, un poquito de JavaScript adicional se encargaría de enviarlo sin necesidad de intervención del usuario.

Eso es lo que tiene el fichero 1.html:


Fichero 3: La página del engaño

Para acabar, la única página que el usuario tiene que ver. Con la promesa de sacar algo útil del router, se pide al usuario que inicie sesión en dicho dispositivo y a continuación pulse un botón. Su nombre, 0.html. Y su contenido:


Y al pulsar el botón se carga, de forma invisible, el formulario fantasma.
… Y éste aprovecha el CSRF para activar el XSS
… … Y el XSS extrae la contraseña y la envía al sitio malicioso

¿Funcionará?

Fichero 4: A la caza del incauto

Para saberlo, nada mejor que probarlo. El usuario recibe la noticia de que hay un sitio web muy chulo que tiene que visitar. Quizá un correo. Quizá buscando en Internet. Quizá...



Siguiendo las instrucciones, la víctima abre otra pestaña, inicia sesión en el router y vuelve a la página engañosa.



Sin que él lo sepa, en el sitio web malicioso.example.com hay un proceso esperando que alguien se le conecte al puerto 9999. Para estas pruebas, basta con un pequeño shell script que extraiga el valor de la contraseña y la muestre en pantalla.



Cuando el usuario haga clic en el botón, algo le aparecerá en la pantalla al atacante: la contraseña de su víctima


Y, sí, esta imagen también la retoqué para que no apareciera la contraseña de mi amigo.

Conclusiones

Moraleja: Aunque no me gano la vida con estas cosas, debo decir que en alguna ocasión que otra me consiguen algo de comer. Y una buena cena siempre sabe mejor cuando te la paga otro.

Bueno, eso y que si tienes un router de éstos, yo no terminaría de estar tranquilo del todo. Sólo iniciaría sesión en él en un navegador sin plugins (porque los plugins pueden acceder al contenido de las páginas y hacer peticiones en tu nombre) y en el que no tuviera abierta ninguna otra pestaña (por si acaso alguna hace cosas como las vistas anteriormente). Y una vez terminada la configuración, cerraría sesión y navegador antes de ponerme a navegar por otros sitios. Y, aún así...

Y si tienes otro router... échale un vistazo. La experiencia me dice que cada uno debería dedicarse a lo que mejor sabe hacer. Y muchos fabricantes de hardware, cuando desarrollan software... bueno... eso.

El router de mi amigo (3 de 4)

Una idea de esas que nunca son buenas

Seguí mirando. Ya tenía un nuevo XSS y no debía olvidarme del CSRF de la vez anterior. De hecho, es más que posible que existan más problemas similares, pues es de esperar que todas las partes de la aplicación tengan un diseño parecido. ¡Apañados vamos!

Había una página que permitía cambiar la contraseña de acceso al router. Lo habitual: pedía una vez la contraseña anterior y dos veces la nueva


Es bueno eso de pedir la contraseña antigua cuando quieres poner una nueva, porque así proteges al usuario, no sea que se despiste y alguien le pille la sesión abierta y se le ocurra alguna travesura. Y también pones más difícil la explotación de algunos Cross Site Request Forgery.

Bueno, claro, eso siempre y cuando no pongas la contraseña antigua en la propia página. Y este router la ponía en el código fuente:



¡No estarías esperando que pusiera la contraseña de mi amigo, verdad! Esta foto la tuve que retocar un poco para no fastidiarle más de la cuenta.

Cuando menos, llamativo sí que es. La versión router del usuario que pega un postit en la pantalla con su contraseña. Y, me pregunté... ¿para qué querrá alguien poner eso ahí? Unas líneas que había un poco más adelante proporcionaban la respuesta:



¡Para hacer esa comprobación! Una vez más, la seguridad puesta en entredicho para realizar comprobaciones del lado del cliente.

Sea como sea, eso de poner una contraseña de acceso dentro del código JavaScript de una página, en texto claro como el agua, no es algo que yo suela recomendar a nadie.

La idea general

Con eso ya tenía bastante para dar el golpe de efecto que me hacía falta para ganarme un ágape por cuenta ajena. El guión, visto desde el punto de vista del atacante sería:

- Creo una página en un servidor gratuito que, aprovechando la vulnerabilidad CSRF, haga que el router registre una marca y modelo de impresora que me permita insertar código JavaScript en la página.

- Ahora, tengo que asegurarme de la víctima ha iniciado sesión en su router.

- Engaño a mi amigo para que visite a continuación mi página maliciosa (la anterior, la del CSRF y todo eso).

- Con eso, consigo que el código JavaScript que escribí se ejecute.

- Dicho código Javascript carga la página de cambio de contraseña. Analiza entonces su contenido y extrae de él la contraseña actual y me la envía a mi servidor.

Y... ¡voila! Ya tengo la contraseña del router de mi víctima.

Otros objetivos


Elegí la contraseña por lo llamativo de obtener algo que debería ser secreto pero... podría haber elegido cualquier otra cosa. La “graciosa” combinación de Cross Site Scripting y Cross Site Request Forgery me permitía obtener cualquier cosa de cualquiera de las páginas que ofrecía el router.

Y, mirando a ver qué tenían, me encontré con cosas llamativas. Cosas como:
- Dirección IP interna y externa (cara a Internet) del router
- Configuración de la WIFI
- Equipos conectados al router, con su IP y su MAC
- Marca, modelo, revisiones del firmware y otros detalles del hardware

Podría haber escogido cualquiera de ellas… o todas juntas. Y, la verdad, si alguien pudiera obtener todos estos datos de un montón de routers tendria acceso a una de las mayores redes Wifi que conozco. A partir de la dirección IP externa podría determinar aproximadamente la posición geográfica de los routers. Si éstos tienen una lista blanca de MAC que pueden conectarse, tendría algunas válidas y podría configurarlas en sus tarjetas de red. El SSID, la passfrase y todo lo demás también lo sabría...

Ideal para quien quiere conectarse a Internet sin estar demasiado controlado.

Pero había mucho más:

¡En una de ellas aparecía el número de teléfono de mi amigo!

Porque resulta que el router “controla” también su teléfono.

Supongo que la pondrán ahí por si se le olvida al dueño. Igual que la contraseña.

Y podía conseguir eso sin modificar nada en el router. Porque modificando su configuración se podría apoderar uno de la red a la que da servicio.

El router de mi amigo (2 de 4)

Comprobaciones en el cliente 


Necesitaba saber si esa comprobación se hacía sólo en la parte cliente, en el navegador, o si el router también lo miraba. De ser sólo en el cliente sería fácil saltarse la restricción. Por ejemplo, en Firefox se podría utilizar las herramientas para desarrollador web del navegador para localizar el punto en que se fija la longitud máxima...

… y cambiar el 16 por 1600...



Y con eso me podría poner a escribir a gusto. Probé entonces a poner como marca y modelo:
";alert("XSS");aaa="

… pero, a pesar de todo mi trabajo, no me hizo caso.


La página comprobaba si en la marca y el modelo había caracteres “raros”. Pero... ¿qué pasaría si yo realizaba la petición directamente al servidor y evitaba el código JavaScript de la página?

Para probar, utilicé un proxy que permite interceptar y modificar las peticiones que realiza el navegador. El que tenía más a mano era WebScarab. Un poco anticuado, sí, pero aún válido para muchas cosas. Lo inicié, indicándole que quería interceptar todas las peticiones, y configuré el navegador para que lo usara.

Después, rellené el formulario de configuración de impresora con valores válidos e hice clic en aceptar. Webscarab me presentó la petición y se puso a mis órdenes:


Yo, confiado, sustituí el valor de ippMake


… y le dije a Webscarab que siguiera adelante con la petición. La respuesta del router traía regalo:


¡Un XSS que permite inyectar directamente dentro del código JavaScript! Eso no se lo encuentra uno todos los días.

MORALEJA: Está muy bien eso de restringir la longitud de los campos de texto y los caracteres que pueden contener, porque así puedes poner difícil las cosas a quien quiera hacer un uso indebido del sistema. Pero si sólo lo haces en la parte cliente, si no realizas también esas comprobaciones en la parte del servidor, estos controles pueden ser fácilmente evitados.

Además, había otra cosita que no me entretuve en estudiar. Parece que el programa del router no comprueba adecuadamente los límites, los tamaños, de las variables, porque una prueba que hice fue poner una cadena un poco más larga de la cuenta como marca/modelo:
12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890

… y por la respuesta del router, parecía que me había metido en el principio de otra cadena, puesto que me mostraba el final de ésta (“/dev/printer0”) concatenada con lo que yo había puesto.



Un overflow que, bien aprovechado, quizá diera mucho juego. Pero eso es otra historia...

El router de mi amigo (1 de 4)

La apuesta


Hace ya tiempo que os conté como ayudé a un amigo que tenía problemas con el botón de activar y desactivar la WIFI en su router. El único problema fue que el apaño lo hice aprovechando una vulnerabilidad CSRF que le encontré al equipo.


Pues bien, hoy tuve tiempo y oportunidad de volver a ver a mi amigo. Todo iba bien hasta que me encaró y me dijo:

- ¿Sabes? Leí aquello que escribiste sobre el CSRF del router y todo eso. Y creo que hablas de “MI” router.

Parecía molesto. Yo, que no me lo esperaba, me las arreglé para poner cara de póker. Pero mi amigo se fue envalentonando:

- ¿Sabes? Creo que lo que cuentas no es verdad. Y si lo es, que no es del todo cierto. Y, aún así, que exageras. ¿Cómo va nadie a hacerse con el control de mi router desde Internet por visitar una página? Es que te gusta hablar por hablar...

Como, a estas alturas, uno ya empieza a ver claras algunas oportunidades, le miré a los ojos y le espeté:

- A que no eres capaz de apostarte una cena.

Eso fue todo lo que hizo falta para herir su orgullo y hacerle caer. Mientras él decía “¡apostada queda!”, yo llamaba por teléfono a casa y decía:

- Iros arreglando, que hoy comemos fuera.

Un XSS... y algo más

Entré al router con el usuario y la contraseña que ya conocía de la vez anterior y me puse a navegar por menús y opciones. Una cosa chula que tenía el cacharro era que podía compartir una impresora entre los equipos de la casa. El formulario para configurar esta opción era bastante sencillo




Cuando analicé el código fuente de la página, comprobé que había un código Javascript que rellenaba los campos con los valores almacenados en el equipo.


Y pensé: ¡No va a ser tan fácil!


Pero sí lo era. El router debe tener una instrucción que genera
var ippMake = "

… y a continuación pone el valor que corresponda a la marca y modelo de la impresora. Finalmente, cierra la cadena y la instrucción al añadir:
";


¿Qué pasaría si pongo una marca y modelo que contenga una comilla doble? Por ejemplo


marca"modelo


Pues... que el programita del router lo copia todo sin hacer más comprobaciones y terminaríamos teniendo un código JavaScript con errores de sintaxis por una cadena mal cerrada.
var ippMake = "marca"modelo";


Pero, oye, eso nos puede permitir insertar instrucciones nuevas dentro del código JavaScript. Si nuestra impresora fuera de una marca rara, digamos:
";alert("XSS");aaa="

Terminaríamos teniendo:
var ippMake = "";alert("XSS");aaa="";

Y eso nos permitiría ejecutar “cosas nuevas” en las páginas del router.

Claro que algún problemilla habría que superar. Y es que el campo en el que se pone la marca y el modelo sólo permite insertar un máximo de 16 caracteres. Pocos para lo que yo necesitaba.


domingo, 10 de marzo de 2013

Experimentos en DataLand (1)

EL ENTORNO

NOTA: No es mi intención sacar aquí ningún tipo de conclusiones. Simplemente, hice algunas pruebas y los resultados me parecieron interesantes. Repetí varias veces las pruebas, por si acaso, pero no sé si a ti te funcionarán igual. Si decides hacer comprobaciones, me gustaría que compartieras cómo quedó la cosa.

La última vez quizá dejé a alguien con la miel en los labios. Empecé a hablar de Seguridad y, acto seguido, concluí el post. Retomemos el tema.

Como dije, me puse a hacer pruebas con eso de las URI de tipo "data". Si llegaste hasta aquí saltando de enlace en enlace y no sabes qué eso de una "URI de tipo data", te adelanto que es una forma de incrustar dentro de un documento HTML una cosa tal como una imagen o un documento. De guardarla DENTRO del propio documento HTML, no en otro fichero aparte. Repito una vez más las referencias que doy siempre:

http://en.wikipedia.org/wiki/Data_URI_scheme


y, barriendo para casa:

 Para las pruebas utilicé un viejo (¡quién lo habría dicho en su momento!) pero aún solvente equipo con CPU Core(TM)2Duo T9300 y 4 GB de RAM, Windows Vista Business Service Pack 2 y Microsoft Security Essentials actualizado. En él tengo instalado VirtualBox y un BackTrack virtualizado que se come 512 MB de RAM y con el que suelo hacerle puñeterías a otras máquinas virtuales.

Y hoy, quizá, también  a la máquina real.

En el BackTrack, cuya IP de hoy es 192.168.27.100, tengo un fichero “con premio”. Un PDF malicioso que, como pude comprobar, es detectado por el antivirus.



ENDATANDO EL FICHERO

Para jugar con él, creé un fichero HTML que le referenciara. Le puse de nombre “capa1.htm” y su contenido quedó así:
<object height="100%" width="100%" data="malicioso.pdf" type="application/pdf"

Entonces, realicé la conversión mediante un comando como
$ ruby poc.rb /var/www/capa1.htm /var/www/prueba1.htm


El resultado, “prueba1.htm”, contiene dentro los datos del PDF codificados en una URI de tipo data:



Pero no me conformé con convertir una sola vez a “data”. Así que creé otro fichero, “capa2.html” con un iframe para contener a “capa1.htm”:
<iframe src="capa1.htm" width="100%" height="100%"></iframe>

Y lo convertí también con:
$ ruby poc.rb /var/www/capa2.htm /var/www/prueba2.htm

El programa “poc.rb” realiza la conversión de forma recursiva, de modo que ahora tenemos el documento PDF pasado a "data" e incrustado en un objeto y todo eso lo vuelto a convertir a "data".

Pero con dos iteraciones tampoco me pareció suficiente e hice otro documento HTML que incrustara a “capa2.htm” y lo llamé “capa3.htm”:
<iframe src="capa1.htm" width="100%" height="100%"></iframe>

Y un “capa4.htm” que incluyera a “capa3.htm”... y así hasta “capa10.htm”, que incluye una referencia a "capa9.htm". Y a todos ellos los pasé por mi “prueba de concepto”. El último, "prueba10.htm" contiene el PDF pasado 10 veces por el procedimiento de conversión a "data". Suficiente por ahora.

¡A JUGAR!

Quería tener los diez ficheros obtenidos así como el documento PDF original en el PC. Así que desactivé momentáneamente la protección en tiempo real del antivirus...



… y me los descargué a una carpeta:



… para a continuación pasarle un análisis en busca de amenazas. "A ver qué me encuentra el Security Essentials":



… Y no tardó en cantar:



Pero cuando terminó y me preocupé por ver en qué ficheros había detectado los problemas...



… ¡sólo en dos de ellos! En el PDF original y en el HTML para el que sólo había realizado la conversión a data una única vez. A pesar de que le había dicho al antivirus que eliminara el "virus", seguía teniéndolo nueve veces en mi sistema.

CONCLUSIONES

Reactivé la protección en tiempo real, que aunque no sea suficiente para garantizar nada por sí sola  (diría un matemático que es "condición necesaria, pero no suficiente"), siempre algo ayuda y me quedé pensando...

En realidad, no tengo del todo claro hasta qué punto sería bueno o malo que un antivirus mirara los documentos codificados como "data" en busca de documentos codificados como "data", en busca de documentos codificados como "data", en busca de documentos codificados como "data", en busca de...

Si lo hiciera, quizá habría malware que, para evitar ser detectado, llenaría carpetas de ficheros con documentos codificados como "data" de forma iterativa cien o doscientas veces. Señuelos con que entretener un buen rato al antivirus mientras "uno hace su trabajo".

Pero, por otro lado, si yo fuera un %#”!~@@!!! de esos que hay por ahí, podría pensar en utilizar esto para muchas cosas. Aparte de ocultar una copia de mi malware, o de otros elementos maliciosos, de forma que no lo encuentren, claro.

Cosas como alojar contenidos no autorizados en los sistemas de almacenamiento corporativos o en servidores webs ajenos. O compartir en redes P2P cosas que no quiero que sean detectadas fácilmente por sistemas automáticos de control.

O, en un caso de robo y fuga de información confidencial, para tratar de enviar un archivo sin ser detectado, codificándolo varias veces a “data” y poniéndolo como destino de un hiperenlace en un correo redactado en formato HTML.

O... ¿Se te ocurre alguna cosa más? Alguna, seguro que sí.

Y... ¿Has probado tus sistemas de protección a ver si detectan cosas como éstas?

La próxima vez os cuento algunas pruebas más que hice.

jueves, 7 de marzo de 2013

El Ruby Mola

Hace tiempo que tengo descuidado este blog. Demasiado.

Pero ha sido por una buena razón. Todo empezó cuando me puse a hacer “limpieza de papeles”. De vez en cuando es bueno mirar qué tiene uno y tirar todos los papeles inútiles que ha ido uno acumulando. Tickets de haber comprado un refresco en el super, folletos de propaganda, documentos secretos que después vas buscando como loco cuando te vuelven a hacer falta...

Y en eso estaba yo cuando me encontré un viejo bloc de notas. Lo abrí y...¡allí estaban! Las notas que fui tomando cuando empecé a aprender a programar en Ruby. ¡Qué recuerdos!

Una cosa de la que me dí cuenta inmediatamente fue que muchas de aquellas cosas las había olvidado y vuelto a descubrir varias veces. Así que decidí crear una página web en Internet e ir poniendo en ella aquello que me parecía más relevante. De ese modo, tarde o temprano, terminaría copiando todo lo que había escrito en mi bloc y podría tirarlo tranquilamente.

Aunque la página web estuviera hecha especialmente para mí, si a alguien más puede valerle... mejor que mejor. Así que decidí darle un enfoque medio didáctico en el que se fueran completando pasos para llegar a un objetivo establecido desde el principio.

El objetivo consistía en crear un analizador de código HTML. Y poco a poco fue saliendo. Cuando estuvo listo, quise hacer algo que entroncara con este blog y me acordé de las URIs de tipo “data:” y cómo las utilicé en su día para realizar ataques de Cross Site Scripting contra webmails basados en RoundCube. De ese tema escribí por aquí y, con más detalle, en "Un informático en el lado del mal" .

Así que utilicé el analizador de HTML para crear un programa que convierte en URIs de tipo “data” las referencias externas de una página web: sus imágenes, sus scripts, sus hojas de estilo en cascada, sus objetos,...

En lo que se tarda en parpadear me pondré a escribir un post sobre cómo se puede aprovechar una herramienta de este tipo para evaluar la seguridad de un equipo y de algunos programas. Porque casi todas las herramientas sirven para más de una cosa. Y supongo que habrá a quien no haya que decirle mucho más para que se vaya haciendo una idea...

Mientras tanto, hago los honores y, aunque le queden cosas por pulir, presento mi blog de Ruby en sociedad. Creo que ya puede empezar a ser útil. Ahí va el enlace:
El Ruby Mola

… y el índice del proyecto de analizador de HTML.
Analizador HTML