Logotipo de Zephyrnet

Detrás de escena: nunca confíes en la opinión del usuario

Fecha:

Este artículo es el primero de una serie de publicaciones que estoy escribiendo sobre la ejecución de varios productos y sitios web SaaS durante los últimos 8 años. Compartiré algunos de los problemas que he enfrentado, las lecciones que he aprendido, los errores que he cometido y tal vez algunas cosas que salieron bien. Quiero saber ¡qué piensas!

En 2019 o 2020, decidí reescribir todo el backend para Bloquear remitente, una aplicación SaaS que ayuda a los usuarios a crear mejores bloques de correo electrónico, entre otras funciones. En el proceso, agregué algunas características nuevas y actualicé a tecnologías mucho más modernas. Ejecuté las pruebas, implementé el código, probé manualmente todo en producción y, aparte de algunas cosas aleatorias, todo parecía estar funcionando muy bien. Ojalá este fuera el final de la historia, pero…

Unas semanas más tarde, un cliente me notificó (lo cual es vergonzoso en sí mismo) que el servicio no estaba funcionando y que estaban recibiendo muchos correos electrónicos que deberían bloquearse en su bandeja de entrada, así que investigué. Muchas veces este problema se debe a que Google elimina la conexión de nuestro servicio a la cuenta del usuario, lo cual el sistema maneja notificando al usuario vía correo electrónico y pidiéndole que se vuelva a conectar, pero esta vez fue algo más.

Parecía que el trabajador backend que se encarga de verificar los correos electrónicos con los bloques de usuarios seguía fallando cada 5 a 10 minutos. La parte más extraña: no había errores en los registros, la memoria estaba bien, pero la CPU ocasionalmente aumentaba en momentos aparentemente aleatorios. Entonces, durante las siguientes 24 horas (con un descanso de 3 horas para dormir; lo siento, clientes 😬), tuve que reiniciar manualmente el trabajador cada vez que fallaba. Por alguna razón, el servicio Elastic Beanstalk estuvo esperando demasiado para reiniciarse, por lo que tuve que hacerlo manualmente.

Depurar problemas en producción siempre es una molestia, especialmente porque no podía reproducir el problema localmente, y mucho menos descubrir qué lo estaba causando. Entonces, como cualquier "buen" desarrollador, comencé a iniciar sesión todo y esperó a que el servidor volviera a fallar. Dado que la CPU aumentaba periódicamente, pensé que no era un problema macro (como cuando te quedas sin memoria) y probablemente lo causaba un correo electrónico o un usuario específico. Entonces traté de reducirlo:

  • ¿Estaba fallando en un determinado ID o tipo de correo electrónico?
  • ¿Estaba fallando para un cliente determinado?
  • ¿Se estrelló en algún intervalo regular?

Después de horas de esto y de mirar registros más tiempo del que me gustaría, finalmente lo limité a un cliente específico. A partir de ahí, el espacio de búsqueda se redujo bastante: lo más probable es que fuera una regla de bloqueo o un correo electrónico específico que nuestro servidor seguía reintentando. Afortunadamente para mí, fue lo primero, lo cual es un problema mucho más fácil de depurar dado que somos una empresa muy centrada en la privacidad y no almacenamos ni vemos ningún dato de correo electrónico.

Antes de abordar el problema exacto, hablemos primero de una de las funciones de Block Sender. En ese momento, muchos clientes pedían el bloqueo de comodines, lo que les permitiría bloquear ciertos tipos de direcciones de correo electrónico que seguían el mismo patrón. Por ejemplo, si desea bloquear todos los correos electrónicos de direcciones de correo electrónico de marketing, puede utilizar el comodín marketing@* y bloquearía todos los correos electrónicos de cualquier dirección que comenzara con marketing@.

Una cosa en la que no pensé es que no todos entienden cómo funcionan los comodines. Supuse que la mayoría de la gente los usaría de la misma manera que yo como desarrollador, usando uno * para representar cualquier número de caracteres. Desafortunadamente, este usuario en particular había asumido que necesitabas usar un comodín para cada personaje que quisieras hacer coincidir. En su caso, querían bloquear todos los correos electrónicos de un determinado dominio (que es una característica nativa que tiene Block Sender, pero no deben haberse dado cuenta, lo cual es todo un problema en sí mismo). Entonces en lugar de usar *@example.com, usaron **********@example.com.

Punto de vista: observar a sus usuarios usar su aplicación...
Punto de vista: observar a sus usuarios usar su aplicación...

Para manejar comodines en nuestro servidor de trabajo, estamos usando la biblioteca Node.js. matcher, que ayuda con la coincidencia global convirtiéndola en una expresión regular. Esta biblioteca entonces se convertiría **********@example.com en algo como la siguiente expresión regular:

/[sS]*[sS]*[sS]*[sS]*[sS]*[sS]*[sS]*[sS]*[sS]*[sS]*@example.com/i

Si tiene alguna experiencia con expresiones regulares, sabrá que pueden volverse muy complicadas muy rápidamente, especialmente a nivel computacional. Hacer coincidir la expresión anterior con cualquier longitud de texto razonable se vuelve muy costoso desde el punto de vista computacional, lo que terminó ocupando la CPU de nuestro servidor de trabajo. Esta es la razón por la que el servidor falla cada pocos minutos; se quedaría atascado al intentar hacer coincidir una expresión regular compleja con una dirección de correo electrónico. Entonces, cada vez que este usuario recibía un correo electrónico, además de todos los reintentos que incorporamos para manejar fallas temporales, nuestro servidor fallaba.

Entonces, ¿cómo solucioné esto? Obviamente, la solución rápida fue encontrar todos los bloques con múltiples comodines uno tras otro y corregirlos. Pero también necesitaba hacer un mejor trabajo al desinfectar la entrada de los usuarios. Cualquier usuario podría ingresar una expresión regular y derribar todo el sistema con un Ataque ReDoS.

Consulte nuestra guía práctica y práctica para aprender Git, con las mejores prácticas, los estándares aceptados por la industria y la hoja de trucos incluida. Deja de buscar en Google los comandos de Git y, de hecho, aprenden ella!

Manejar este caso particular fue bastante simple: elimine los caracteres comodín sucesivos:

block = block.replace(/*+/g, '*')

Pero eso aún deja la aplicación abierta a otros tipos de ataques ReDoS. Afortunadamente, también hay varios paquetes/bibliotecas que nos ayudan con estos tipos:

Usando una combinación de las soluciones anteriores y otras medidas de seguridad, pude evitar que esto vuelva a suceder. Pero fue un buen recordatorio de que nunca se puede confiar en la entrada del usuario y que siempre se debe desinfectar antes de usarla en su aplicación. Ni siquiera sabía que esto era un problema potencial hasta que me pasó a mí, así que espero que esto ayude a alguien más a evitar el mismo problema.

¿Tiene alguna pregunta, comentario o quiere compartir una historia propia? Comuníquese con Twitter!

punto_img

Información más reciente

punto_img