HackMyVM - Catland


Creado
HackMyVM - Catland

Máquina Linux nivel medio. Tendremos fuerza bruta en logins con wordlists propios, LFItoRCE mediante ZIP, temitas del GRUB con más fuerza bruta y análisis de código para encontrar problemitas en el sistema.

TL;DR (Spanish writeup)

Creada por: cromiphi.

Descargamos la máquina acá: HackMyVM - Catland.

Mucho cat, poco strings :P

Tendremos una web con gatos y el nombre de su cuidadora, jugaremos con fuzzeos para encontrar un subdominio el cual tiene un login-panel, crearemos el entorno para generar ataques de fuerza bruta personalizados, empleando a la cuidadora y a wordlists relacionadas con su nombre y gustos, así, conseguiremos una sesión en el panel administrativo como el usuario laura.

Adentro encontraremos dos cosas, una subida de archivos .zip y .rar yyyy un LFI, juntaremos los dos hallazgos para crear un LFItoRCE guapetón mediante wrappers en PHP que lean, extraigan y ejecuten el contenido del archivo comprimido, esto para obtener ejecución remota de comandos como el usuario www-data en el sistema.

Ya en el sistema vamos a encontrar credenciales para interactuar con el servicio MySQL, en él tendremos una tabla que indica (¿por qué ahí adentro?, ni idea klasdjfkl) que se debe cambiar la contraseña al GRUB, jugamos en el sistema para ver la contraseña actual del GRUB yyyy en lugar de cambiarla, la crackeamos, esto para obtener una terminal como el usuario laura.

Como último paso tendremos asignado un permiso que permite ejecutar un script en Python para interactuar con Reddit desde una consola, como cualquier usuario del sistema, el tema es que en la lógica inicial del programa vemos unos llamados a unos paquetes yyy al buscar esos paquetes (librerías) en el sistema nos damos cuenta de que tenemos acceso de escritura contra una de ellas :O Aprovecharemos esto para ejecutar comandos como el usuario root y generar una /bin/bash.

La idea inicial de esta locura es tener mis “notas” por si algun día se me olvida todo (lo que es muuuy probable), leer esto y reencontrarme (o talvez no) 😄 La segunda idea surgio con el tiempo, ya que me di cuenta que esta es una puerta para personitas que como yo al inicio (o simplemente a veces) nos estancamos en este mundo de la seguridad, por lo que si tengo la oportunidad de ayudarlos ¿por qué no hacerlo?

Un detalle es que si ves mucho texto, es por que me gusta mostrar tanto errores como exitos y tambien plasmar todo desde una perspectiva más de enseñanza que de solo pasos a seguir. Sin menos, muchas gracias <3

¡Qué sabrosura!

  1. Reconocimiento.
  2. Enumeración.
  3. Explotación.
  4. Movimiento Lateral: www-data -> laura.
  5. Escalada de privilegios.

Reconocimiento #

Como es usual en esta plataforma, descargamos la VM, la cargamos en el virtualizador, la iniciamos y ahora viene la parte juguetona, encontrar su IP.

En este caso usaremos la herramienta nmap para ello.

Tengo una red NAT configurada para que toda máquina que agregue a esa red tome una IP entre el rango 192.168.100.0/24 (192.168.100.1 - 192.168.100.254), así que hagamos un escaneo sobre ese rango a ver cuál suena llamativa:

nmap -sn 192.168.100.0/24
Parámetro Descripción
-sn Envía trazas ICMP (un ping)

Y obtenemos el gateway de la red, mi ip y una que ni idea, así que esa debe ser la de la máquina Catland:

# Gateway
Nmap scan report for 192.168.100.1
Host is up (0.00026s latency).
# Mi IP
Nmap scan report for 192.168.100.7
Host is up (0.000085s latency).
# Catland?
Nmap scan report for 192.168.100.14
Host is up (0.00021s latency).

Pues teniendo esa IP vamos a comenzar ahora sí, hagamos un escaneo de puertos y de ahí en la validación sabremos si tenemos la máquina víctima:

nmap -p- --open 192.168.100.14 -v -oG initScan
Parámetro Descripción
-p- Escanea todos los 65535
–open Solo los puertos que están abiertos
-v Permite ver en consola lo que va encontrando
-oG Guarda el output en un archivo con formato grepeable para usar una función extractPorts de S4vitar que me extrae los puertos en la clipboard

Nos encontramos:

Puerto Descripción
22 SSH: Nos permite obtener una terminal de manera segura.
80 HTTP: Mantiene un servidor web (página web).

Ya tenemos los puertos abiertos, pero esta chévere tener más info, nmap nos ayuda con eso, digámosle que nos muestra las versiones de cada servicio y también que usé unos scripts que tiene por default a ver si ellos encuentran algo más a lo que ya tenemos:

+ ~ +(Usando la función extractPorts (referenciada antes) podemos copiar rápidamente los puertos en la clipboard, en este caso no es necesario (ya que tenemos pocos puertos), pero si tuviéramos varios evitamos tener que escribirlos uno a uno

» extractPorts initScan 
[*] Extracting information...

    [*] IP Address: 192.168.100.14
    [*] Open ports: 22,80

[*] Ports copied to clipboard

)+ ~ +

nmap -p 22,80 -sCV 192.168.100.14 -oN portScan
Parámetro Descripción
-p Escaneo de los puertos obtenidos
-sC Muestra todos los scripts relacionados con el servicio
-sV Nos permite ver la versión del servicio
-oN Guarda el output en un archivo

Tenemos algunas cositas relevantes:

Puerto Servicio Versión
22 SSH OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80 HTTP Apache httpd 2.4.54
  • Nos filtra también el título de la página web: Catland, así que tamos bien.

De resto no hay nada más, por lo que metámosle mano y rompamos esto…

Enumeración #


Recorremos el servidor web 📌

Démosle un vistazo al sitio web:

Hablan con respecto a ayudar a gatos, también nos proveen de una galería de imágenes al dar clic en Gallery:

En esta galería aparentemente no hay nada interesante, pero prestando atención destaca algo. El nombre de uno de los archivos tiene un nombre de una mujer, pueda que no sea nada, pero guardémoslo por si algo:

Lo siguiente que notamos al ya hacer un poco de cositas profundas, es que al ver los metadatos de cada imagen, se nos revela en la imagen más grande (/images/tired-cat.jpeg) el software y versión con la que ha sido creada:

» exiftool tired.jpeg 
...
Comment                         : CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), default quality.
...

Buscando en internet llegamos a varias cosas relacionadas con una librería de PHP llamada PHP GD library:

Pero nada, esto podría ser probado o tenido en cuenta en caso de encontrar/tener alguna manera de subir archivos, pero por ahora no…

Este comentario me hizo caer en cuenta de algo, pueda que existan objetos alojados en el servidor, pero que estén fuera de la vista, así que hagamos fuzzeito a ver que encontramos:

dirsearch.py -u http://192.168.100.14/

Pero superficialmente no vemos nuevos objetos. La otra prueba que podemos hacer es validar distintas extensiones o wordlists a ver si pasa algo distinto (pero no).

Lo siguiente a probar con ayuda de (en este caso, pero hay infinidad de herramientas) ffuf o wfuzz es fuzzear posibles subdominios. Pero claro, para ello necesitamos saber o intuir el nombre del dominio, en este caso al llamarse catland la máquina, podemos pensar que quizás la web resuelva contra el dominio catland.hmv, por lo que los subdominios en caso de existir podrían ser ALGO.catland.hmv, con base en esto, juguemos:

ffuf -c -w /opt/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://192.168.100.14 -H 'Host: FUZZ.catland.hmv' -fs 757

Usando un wordlist de SecLists le indicaremos que por cada palabra vaya al header Host y genere un posible subdominio, despues haga la petición y revise si alguno resuelve correctamente contra la dirección IP 192.168.100.14, para evitar falsos positivos quitamos las respuestas con tamaño 757.

Ejecutamos yyyyy:

Existe aparentemente el subdominio admin.catland.hmv con contenido distinto al de 192.168.100.14 (catland.hmv). Entonces veamos si es verdad, agreguémoslo al objeto /etc/hosts para que el sistema entienda que al hacer una petición contra ese subdominio debe responder con respecto a la dirección IP:

» cat /etc/hosts
...
192.168.100.14  catland.hmv admin.catland.hmv
...

Y al hacer una petición en la weeeeeb… Como que encuentra el recurso, pero nos redirige de nuevo a la principal (catland.hmv), si verificamos con cURL notamos la razón:

» curl http://admin.catland.hmv/redirect.js
function redirectToSubdomain() {
  window.location.replace('http://catland.hmv');
}

Lo que hace es que en la respuesta ejecuta la función redirectToSubdomain que hace eso, redirigirnos…

Pero saltarse esto realmente es sencillo, apoyémonos de BurpSuite.

Saltando redirects con BurpSuite 📌

Los pasos son sencillos:

➡️➡️➡️ Abrimos BurpSuite.

➡️➡️➡️ Vamos al navegador y activamos el proxy para poder comunicarnos con Burp, ya sea con la config propia del navegador o con ayuda de (por ejemplo) la extensión FoxyProxy.

➡️➡️➡️ Volvemos a Burp, vamos a Proxy > Options > Match and Replace.

➡️➡️➡️ Agregamos una regla para que cuando se haga una petición y en la respuesta encuentre redirectToSubdomain lo remplace por vacío:

Guardamos.

➡️➡️➡️ Volvemos al navegador y realizamos una petición contra el subdominio:

Y LISTOOOOO! Nos bypasseamos ese redirect para finalmente llegar a un login empresarial, a jugar.

Explotación #

Después de probar muuuuuuchas cosas:

  • Inyecciones y bypasses.
  • Generar posibles errores.
  • Validación de headers.
  • Fuzzing.
    • Encontramos varios objetos PHP y una carpeta /uploads (me gusta), pero estamos muy limitados, ya que no tenemos una sesión para interactuar.
  • Fuerza bruta contra laura y demás posibles usuarios usando contraseñas alojadas en listas de palabras públicas (ej: rockyou.txt).

Pero nada, nada de nada…

Pensando y pensando se me ocurrió profundizar un poco con el tema de la fuerza bruta (que casi no se testea, ya que pocas (muy pocas) veces da resultado).

Pensé en generar contraseñas “custom” con respecto al usuario o temas relacionados, por ejemplo nos encontramos con el nombre laura, el tema cat (gato), etc. Pues se podría crear una wordlist personalizada con contraseñas tipo:

laura1
laura2
...
lauracat
Laur@cat
...

Solo que claro, serían listas muuuuy largas, pero es lo que hay :P Lo mejor es buscar herramientas que hagan ese trabajo, combinen los símbolos, mayúsculas, números, etc.

Después de frustrarme con crunch, llegue por internet a esta tool:

📚 “pydictor A powerful and useful hacker dictionary builder for a brute-force attack.”

Efectivamente, lo que estamos buscando, una tool que genere lista de palabras basadas en una “semilla” (en nuestro caso, laura, cat o cats).

Para un wordlist sencillo que relacione únicamente a laura se usaría así:

python3 pydictor.py --extend laura --output pydictor_wordlist.txt

Y si abrimos el archivo, tendría más o menos:

...
laura#laura
laura#Laura
...
LAURA!@#54321
LAURA!@#admin
...
2019Laura    
2010.Laura
...
Laura_123abc  
Laura.111
...

Listos, pues a ver, hay muuuuchas maneras de hacer esta fuerza bruta, pero como lo venía trabajando (en mis pruebas internas :P) sería mediante ffuf:

ffuf -c -w ./results/pydictor_wordlist.txt -H 'Content-Type: application/x-www-form-urlencoded' -u http://admin.catland.hmv/index.php -d "username=laura&password=FUZZ" -fs 1068

Con esto, le pasamos el wordlist recién generado, le pedimos que haga peticiones POST enviando username y password y seteamos la cabecera Content-Type indicándole que le estamos enviando parámetros basados en llaves y valores.

Pero al ejecutarlo no obtenemos nada (ni con cat y demás variantes), así que probemos a agrandar las opciones de palabras (en este caso obtenemos más de 30.000 líneas, antes teníamos 1.500):

python3 pydictor.py --extend laura --level 1 --output pydictor_wordlist.txt

Volvemos a probar con ffuf yyyyyyyyyy:

eSTA TOMAndO un PaTróN dE creDENcIaL coMO VALIDOOOOOO!

Si probamoooooos:

Estamos dentroooooowowowow.


Subidas y lecturas peligrosas de archivos 📌

Adentro vemos dos cosas interesantes:

💠 Hay un archivo /upload.php el cual permite subir archivos, pero no de cualquier tipo, solo .rar o .zip.

Esto es curioso y toma fuerza por lo siguiente que encontramos…

💠 Al seleccionar una página en /user.php (el de la imagen de arriba) y dar clic en Go vemos que la URL cambia:

http://admin.catland.hmv/user.php?page=card

Esto lo que me da a entender es que está buscando una página (?page=) llamada card (card, quizás en la lógica sea un archivo tal que card.txt o card.html y que esté concatenando strings) para mostrar su contenido en la web.

POR LO QUEEEEEEEEEEEEE, podemos probar cositas, quizás no está bien sanitizado y logramos ver objetos del sistema (Path Traversal) o quizás peor, hacer que se ejecuten (LFI).

Validemos con el clásico archivo de usuarios en Linux, /etc/passwd:

Y sí, existe inicialmente un Path Traversal, si probamos a leer upload.php (o alguno de los objetos que encontramos en el fuzzing, config.php, index.php) vemos que lo interpreta, por lo que existe también un LFI (:

Destacamos del objeto /etc/passwd el usuario laura y root como unicos que pueden obtener una terminal, esto para anotarlos y gaurdarlo.

Lo siguiente sería ver el contenido de esos archivos .php sin que sean interpretados, pero es que hay algo aún más juguetón a intentar:

🔥 Aunque les dejo este script en Bash donde usé el wrapper php://filter/convert.base64-encode/resource para obtener el contenido de un objeto en base64, ya queda en nosotros decodear y ver el texto plano.


LFItoRCE usando wrappers zip:// y rar:// 📌

Rápidamente:

Un wrapper en PHP simplemente es una forma de agregar funcionalidades para interactuar con recursos. Por ejemplo para jugar con archivos locales (el que estamos usando) o remotos existen wrappers, para peticiones web HTTP(S) tambien y para otras cositas.

La explotación es muy sencilla:

  • Creamos un objeto .php con contenido malicioso.
  • Creamos un objeto .zip (o .rar, yo lo haré con zip) que contenga ese .php.
  • Buscamos una manera de subir archivos.
  • Encontramos donde se están alojando los archivos.
  • Mediante el LFI y los wrappers leemos el objeto .php que está dentro del .zip, con esto lo que hacemos es ejecutar su contenido:

    http://[URL]?[PARAMETRO]=zip://[NOMBRE_ARCHIVO_ZIPoRAR]#[NOMBRE_ARCHIVO_PHP_MALICIOSO]
    
    // Pero para que sea correctamente interpretado hay que convertir el # a URL Encode:
    http://[URL]?[PARAMETRO]=zip://[NOMBRE_ARCHIVO_ZIPoRAR]%23[NOMBRE_ARCHIVO_PHP_MALICIOSO]
    
    # Ejemplo en neustro caso
    http://admin.catland.hmv/user.php?page=zip://guardadito.zip%23payload.php
    
  • Y listo, se ejecutaría el contenido malicioso del .php (:

Así que hagámosle…

🕐 Intentemos que nuestro archivo payload.php ejecute el comando id:

echo '<?php system("id"); ?>' > payload.php

🕑 Creamos el .zip:

zip guardadito.zip payload.php

🕒 Ya tenemos la manera de subir archivos, así que vamos /upload.php y subimos el .zip.

🕓 También tenemos la ruta donde están siendo alojados los objetos subidos: /uploads/.

🕔 EJECUTAMOS EL RCE con ayuda de los wrappers y el LFI:

http://admin.catland.hmv/user.php?page=zip://guardadito.zip%23payload.php

Peeero no obtenemos nada:

Solo que entre las pruebas, si llamamos al archivo .zip sin wrappers obtenemos:

http://admin.catland.hmv/user.php?page=uploads/guardadito.zip

:O Por lo que entendemos que el objeto .zip esta no solo siendo leído, si no, descomprimido automáticamente, y como el contenido es un .php, pues el servidor web hace su trabajo, lo interpreta!

Por lo que listoooones, ya tenemos ejecución remota de comandos, te dejo la tarea de como obtener una terminal, solo te ayudo con como hacerla bonita y completamente funcional (:

Lo curioso es que realmente la explotación con el wrapper si funciona, pero a veces, te dejo un script que me gustó y con el que siempre logré ejecutar el LFI+ZIPwrapper=RCE: rce_zipWrapper_adminland.py


bRUG : www-data -> laura #

Ya en el sistema recordamos uno de los archivos que vimos mediante el LFI:

  • config.php

...
$database = "catland";
$username = "admin";
$password = "catlandpassword123";
...

En él hay credenciales válidas contra la base de datos catland, probemos conexión:

Recorriendo la base de datos catland notamos una tabla llamada comment y en su contenido:

Jmmmm, ¿existe una tabla que tiene un solo comentario e indica “cambiar la contraseña de grub”? Así sin pelos en la lengua lo puso…

Es probable que hayamos escuchado el término GRUB (y no, no es un usuario :P):

Como nos indica Nullet en su respuesta a un hilo de stackoverflow, el GRUB (GNU GRand Unified Bootloader) es el software que se encarga de cargar el sistema operativo (o los sistemas operativos) de un computador.

Bien ya tenemos ideas, ahora necesitamos saber como cambiar la contraseña del GRUB, buscando caemos acá:

Y nos dice:

“Main configuration file of GRUB is grub.cfg

Y sip, encontramos ese archivo en el sistema:

www-data@catland:/$ find / -name "grub.cfg" 2>/dev/null
/boot/grub/grub.cfg

En su contenido, líneas majestuosas aparecen:

Están las credenciales (usuario root y contraseña hasheada con el algoritmo pbkdf2) necesarias para ingresar a la interfaz del GRUB :O

Siguiendo la guía de modificación de la contraseña, llega una parte donde necesitamos escribir archivos en partes donde no tenemos permisos, por lo que lo único que queda por validar es:

  • ¿Y si esa contraseña es muy simple y al intentar crackear el hash logramos algo? Metámosle…

Tomamos el hash desde grub.pbkdf2.sha... hasta el final de la cadena ...BBC y lo guardamos en un archivo, lo llamaré hash_grub_pbkdf2.txt. Lo siguiente es ya sea con hashcat o con John The Ripper intentar romper ese hash mediante fuerza bruta y así obtener la contraseña sin cifrar, usaré john (le pasaré un wordlist muy famoso en el mundo):

john -w:/usr/share/wordlists/rockyou.txt hash_grub_pbkdf2.txt

En la ejecución él interpreta que tipo de hash puede ser y empieza a probar, finalmenteeeee:

...
Warning: detected hash type "PBKDF2-HMAC-SHA512", but the string is also recognized as "HMAC-SHA256"
Warning: detected hash type "PBKDF2-HMAC-SHA512", but the string is also recognized as "HMAC-SHA512"
...
...
berbatov         (?)
...

TENEMOS LA CONTRASEÑA SIN CIFRAAAAAAR para el usuario del GRUBBBBB! Solo que claro, antes de jugar con el GRUB, y si probamos y quizás esa contraseña también pertenece a otro usuario, tal vez a laura o al propio root :O Haciendo reutilización de credenciales tenemos:

SoMOS LAurAAAaaa!

Como ya tenemos credenciales, podemos generar una terminal mediante SSH: ssh laura@catland.hmv.


Escalada de privilegios #

Validando que permisos tenemos en el sistema como otros usuarios, notamos un binario que podemos ejecutar como cualquier usuario:

laura@catland:~$ sudo -l
Matching Defaults entries for laura on catland:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User laura may run the following commands on catland:
    (ALL : ALL) NOPASSWD: /usr/bin/rtv --help

Browse Reddit from your terminal, uhhh, re chévere que exista esto jaja, loquito.

Lo llamativo es que realmente no tenemos permiso par interactuar con el binario, únicamente para ejecutar el menú de ayuda: --help

Esto acorta un montón el enfoque de ataque, ya que debemos pensar pocas cosas:

  • Quizás hay una instrucción de la que podemos aprovecharnos cuando se ejecuta el help.
  • Quizás el menú de help tiene una trampa y debemos revisar muy atentos.
  • Quizás debamos modificar algún archivo que sea llamado cuando se ejecuta el help.
  • Quizás, quizás, quizás.

Así estuve un buen rato, probando cosas, releyendo el menú de ayuda, interactuando sin llegar a ningún lado.

Y algo básico que se me olvido hacer fue ver qué tipo de archivo era el objeto:

laura@catland:~$ file /usr/bin/rtv
/usr/bin/rtv: Python script, ASCII text executable

Uhh, pues es algo que podemos leer con claridad, en su contenido tenemos:

#!/usr/bin/python3
# EASY-INSTALL-ENTRY-SCRIPT: 'rtv==1.27.0','console_scripts','rtv'
import re
import sys

# for compatibility with easy_install; see #2198
__requires__ = 'rtv==1.27.0'

try:
    from importlib.metadata import distribution
except ImportError:
    try:
        from importlib_metadata import distribution
    except ImportError:
        from pkg_resources import load_entry_point

def importlib_load_entry_point(spec, group, name):
    dist_name, _, _ = spec.partition('==')
    matches = (
        entry_point
        for entry_point in distribution(dist_name).entry_points
        if entry_point.group == group and entry_point.name == name
    )
    return next(matches).load()

globals().setdefault('load_entry_point', importlib_load_entry_point)

if __name__ == '__main__':
    sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
    sys.exit(load_entry_point('rtv==1.27.0', 'console_scripts', 'rtv')())

Y acá nos quedamos en un lindo rabbit-hole con el que interactuábamos con entry_points (que en pocas palabras permiten crear llamados a funciones o scripts de PY desde la consola/terminal de comandos) e incluso lográbamos desde el entorno de laura hacer que el script /usr/bin/rtv tomara (si se fijan en la última línea llama 3 entry points) el entry point rtv y lo ejecutará, para ello realizábamos un Path Hijacking donde rtv (rtv.py) sería un objeto con contenido malicioso; al ejecutar /usr/bin/rtv buscaría el entry point rtv y ejecutaría su malicia, así fue y todo bien, pero ya probando con sudo /usr/bin/rtv no lográbamos nada, así que toco cambiar de enfoque.

Dando vueltas finalmente caemos en algo obvio (pero poco probado también):

En el script hay unos llamados a librerías e instrucciones antes de llegar a la parte de los entry points y de cargar lo que sería el --help, por ejemplo vemos que llama la librería importlib, de ahí entra al script metadata y de él usa la función distribution, esto puede ser irrelevante, pero claro, si no tenemos más a donde mirar y probar, pues busquemos esa librería y el script, pueda que podamos interactuar o ver algo llamativo en él:

laura@catland:~$ find / -name 'importlib' 2>/dev/null
/usr/lib/python3.9/importlib

Ahí ta, listemos:

OPAAAAAAAAAAAAAAAAAAAAAA!! ¿Qué entiendes por eso?

SIIIII! El objeto metadata.py le permite a cualquier usuario del sistema modificar su contenido 😯

Así tambien podriamos haber encontrado todos los archivos a lo que nuestro usuario tiene acceso de escritura: find / -writable -type f 2>/dev/null | grep -vE "proc".

No, pues regalado, abramos el archivo, busquemos la función distribution y metámosle un comando que al ser ejecutado nos devuelva una /bin/bash:

...
def distribution(distribution_name):
    os.system('/bin/bash')

    """Get the ``Distribution`` instance for the named package.
    ...
...

Y ejecutamos la instrucción con sudo:

LISTOOOOOS! Veamos las flags…

Y nos largamos!

bastante Una divertida máquina (: Toca varias cosas que no había visto o probado, lo que me permite ampliar mi campo de acción contra la siguiente máquina, brutal!

  1. El tema del bruteforce contra el usuario me gusto, además que puede llegar a ser real (más de lo que crees :P).
  2. Jugar con el .zip creo que quedo medio raro en la lógica, pero de igual forma la intención de ese ataque es muy chévere.
  3. También aprendimos cositas del GRUB y de revisar todo lo que involucra un script :P

Por hoy es todo! Nos leemos en la siguiente edición de este blogsito, muchos abrazos y energía, a darle duro que ya casi logras lo que quieres, siempre estás avanzando!

A darle duro yyyyyyyyyyyyy A ROMPER DE TODOOOOO!

Lanz

Lanz

Holap, simplemente quiero compartir contigo mis notas y que quizás, las tomes como apoyo. Este mundo es un camino raro, complicado a veces, pero divertido, diviertete (: (y entiende que estas haciendo :P)

Comments

comments powered by Disqus