freeCodeCamp.org Discourse se está derrumbando por scripts de spammers

Comenzamos a observar cargas elevadas en el foro Discourse de freeCodeCamp.org el 21 de junio. Nuestra carga promedio de CPU comenzó a aumentar hasta el punto en que todo el foro quedó sin respuesta.

Nuestra investigación inicial

  1. Actualizamos Discourse desde la versión Estable a la última versión de Pruebas Aprobadas (Beta), que se recomienda para obtener todas las correcciones de rendimiento más recientes. Parecía que no había mucha diferencia.

  2. Eliminamos algunos de los plugins antiguos para ayudar a diagnosticar el problema, y no parece ser la causa de la lentitud.

  3. Para descartar cualquier mala configuración, probamos esto con el tema predeterminado y revisamos los /logs, donde encontramos algunas excepciones:

    Excepción del trabajo: no se pudo obtener una conexión del grupo dentro de 5.000 segundos (se esperó 5.006 segundos); todas las conexiones agrupadas estaban en uso

Esto parece deberse a que la instancia ya podría estar sobrecargada con muchas tareas de larga duración.

El contenedor de Discourse (upstream) se ejecuta en Digital Ocean y parece estar funcionando a alta temperatura en los 6 vCPUs. Actualmente está ejecutándose en la versión: 2.5.0.beta7

Investigación adicional

  1. Esta mañana nuestros moderadores nos informaron que hubo un aumento significativo en la cantidad de cuentas de spam, lo que nos llevó a creer que podría tratarse de un ataque dirigido.

  2. Para intentar mitigar el problema, pusimos el foro en modo solo lectura. A pesar de esto, la tasa de utilización de recursos sigue siendo bastante alta, superior al 60%.

  3. Según lo que podemos determinar: desde que comenzó esto, hemos observado una menor cantidad de solicitudes por segundo y una mayor tasa de solicitudes actuales en nuestro proxy:

  1. El volumen de tráfico hacia The freeCodeCamp Forum - Join the developer community and learn to code for free. no ha cambiado significativamente según Google Analytics.
  2. Ninguna de las otras aplicaciones en el mismo proxy parece verse afectada. Nuestra plataforma /news y /learn están operando con normalidad.
  3. Intentamos agregar algo de limitación de tasa adicional en el proxy, pero no hizo mucha diferencia, así que la eliminamos (para permitir que los usuarios normales sigan accediendo a nuestro sitio).
  4. También hemos movido las redirecciones de nuestro antiguo foro.freecodecamp.com fuera del foro .org, ya que notamos que ese antiguo subdominio (que no hemos usado en 2.5 años) tuvo un gran pico de tráfico comenzando hace unos días.

En este momento, con aproximadamente 400 usuarios en tiempo real, las estadísticas se ven así:

Observaciones sobre el comportamiento del spammer

Los spammers parecen estar ejecutando algún tipo de script que crea nuevas cuentas en una instancia de Discourse, luego espera varios días. Después, esas cuentas de repente se vuelven activas. Comienzan a publicar nuevos hilos con enlaces a un sitio web. (¿Quizás para construir backlinks?)

Algunas de estas cuentas fueron creadas todo el camino de vuelta en marzo.

Así es como se ve uno de sus mensajes de spam, aunque hay muchas variantes que enlazan a muchos sitios web:

Cualquier consejo será bienvenido aquí.

14 Me gusta

Esto merece mucha más atención

¡Muchas gracias por compartir!

Una de mis instancias favoritas de Discourse, freeCodeCamp, se ha detenido completamente en los últimos dos días.

5 Me gusta

No creo que el problema principal de la carga pesada sea causado por los spammers.

Aquí está el porqué:

El uso normal de Discourse (sin API) solo funciona con navegadores modernos, ya que depende en gran medida de JavaScript. Google Analytics también utiliza JavaScript para recopilar diversas informaciónes de los usuarios (y no necesita soporte de JavaScript moderno para ejecutar el código de análisis). Si Google Analytics no puede detectar la actividad del usuario, entonces Discourse tampoco debería poder servir su contenido y funciones.

Aquí hay una captura de un bot cuando uso una biblioteca antigua de navegador sin cabeza (phantomjs) para acceder al sitio de Discourse:

Y aquí está con una biblioteca más moderna (puppeteer):

  1. Es simplemente extraño que alguien pueda publicar (sin API) respuestas en Discourse mientras Google Analytics no puede detectarlos.
  2. Por lo general, los spammers utilizan proxies públicos para hacer cosas sucias, y creo que tu Cloudflare es lo suficientemente inteligente como para detener a “los visitantes malos” antes de que realmente lleguen a tu aplicación.

¿Has visto trabajos en cola altos en el proceso de Sidekiq?

2 Me gusta

Esto ignora el hecho de que más de la mitad de las personas en las comunidades tecnológicas tienen alguna extensión de bloqueo de anuncios, que siempre bloquea Google Analytics de forma predeterminada.

18 Me gusta

Incluyendo el nuevo macOS.

Era solo un rumor desagradable. Véase a continuación.

5 Me gusta

¿Está Akismet habilitado? ¿Puedes activar la moderación para el nivel de confianza 1?

2 Me gusta

Sí, vimos que los trabajos subían y bajaban como parte de mi investigación inicial. Luego procedimos a revocar todas las claves de usuario.

Sin embargo, parece que no ha tenido ningún efecto en la estabilidad.

Aunque estamos trabajando muy de cerca con el equipo de Discourse para resolver esto.

5 Me gusta

No veo por qué es extraño que pueda haber solicitudes sin que las analíticas basadas en JavaScript muestren nada.
El spammer puede usar los mismos endpoints que usa JavaScript, pero sin JavaScript. Las analíticas basadas en JavaScript no se activarían.

3 Me gusta

Es mucho más probable que se trate de una interacción con un complemento o de una configuración de proxy incorrecta. No hemos tenido ningún éxito con los ‘spammers’ en nuestro alojamiento.

Dado que se trata de un sitio de programación, también podría tratarse de ‘programadores’ intentando hacer algo extraño en el foro.

Pero no: los spammers no son un problema generalizado en las miles de sitios que alojamos.

3 Me gusta

Sí, me inclino a pensar que se debe a la configuración (un plugin defectuoso o una configuración de proxy incorrecta) o a que alguien está manipulando el foro.

La segunda opción es más probable, dado todos los patrones observados.

Por lo que he notado, las cuentas de spam se han creado durante un largo período de tiempo y están intentando agregar enlaces (¿para obtener backlinks?) en sus biografías y todo tipo de rarezas.

Además, podría haber un problema de raspado de datos (scraping), ya que hemos puesto el sitio en modo solo lectura, configurado una caché en el proxy y aplicado limitación de tasa. Sin embargo, el uso de recursos sigue siendo alto en el contenedor upstream.

No obstante, no descartaría que nuestra configuración también sea incorrecta. Utilizamos subrutas y Cloudflare encima de nuestro proxy inverso, lo cual tradicionalmente no es la configuración más eficiente que recomienda Discourse.

1 me gusta

image

Un robo del 42,5 % es realmente alto, incluso si tú eres quien está causando problemas en ese hipervisor. Esto parece un vecino ruidoso. En tu lugar, contactaría a DO y les pediría que muevan el droplet a otro hipervisor.

10 Me gusta

Estoy seguro de que probablemente ya lo estás haciendo, pero solo para asegurarme, te sugiero monitorear las conexiones TCP/UDP abiertas resumidas por IP a nivel del sistema operativo. Si hay una alta carga de CPU, debería mostrarte una gran cantidad de conexiones abiertas al servidor web.

¿Hay algún patrón extraño en production.log?

1 me gusta
4 Me gusta

Hola Quincy @ossia

Hagamos una pausa rápida y analicemos esto desde una perspectiva profesional de ciberseguridad, sin especulaciones ni intentos de “agarrarse a pajas”.

El concepto clave en todas las tareas de ciberseguridad es la “conciencia situacional”; en este caso, se denomina “conciencia situacional cibernética” (CSA).

Para saber “qué está sucediendo” de manera definitiva, debes desarrollar el mejor conocimiento situacional posible, sin especulaciones ni suposiciones. Solo los hechos.

¿Cómo se hace esto?

Bueno, muy brevemente:

Bueno, muy brevemente:

Lo hacemos fusionando información de todos nuestros sensores. Para las aplicaciones basadas en la web, esto normalmente proviene de los archivos de registro y los datos de sesión. No creo (por lo que recuerdo) que Discourse mantenga información de sesión en la base de datos PG (la última vez que revisé no había una tabla de sesiones como en algunas aplicaciones web LAMP), pero eso no es un obstáculo en absoluto.

Tienes casi todo lo necesario en los archivos de registro de nginx tanto para tu proxy inverso fuera del contenedor (recuerdo haber leído en este tema que estabas usando nginx como proxy) como la misma información de registro dentro del contenedor. En ambos casos, el archivo de registro se encuentra aquí, en la configuración estándar OOTB:

Aquí hay un ejemplo en una de nuestras configuraciones (fuera del contenedor) para el proxy inverso:

# cd /var/log/nginx
# ls -l 
total 779964
-rw-r----- 1 www-data adm         0 Jun 17 06:25 access.log
-rw-r----- 1 www-data adm 660766201 Jun 25 18:26 access.log.1
-rw-r----- 1 www-data adm 107367317 Jun 17 03:18 access.log.2.gz
-rw-r----- 1 www-data adm  21890638 May 21 03:08 access.log.3.gz
-rw-r----- 1 www-data adm   7414232 May  5 07:26 access.log.4.gz
-rw-r----- 1 www-data adm     63289 Apr 18 09:12 access.log.5.gz
-rw-r----- 1 www-data adm         0 Jun 17 06:25 error.log
-rw-r----- 1 www-data adm    904864 Jun 25 18:19 error.log.1
-rw-r----- 1 www-data adm     96255 Jun 17 03:17 error.log.2.gz
-rw-r----- 1 www-data adm     79065 May 21 02:58 error.log.3.gz
-rw-r----- 1 www-data adm     70799 May  5 06:54 error.log.4.gz
-rw-r----- 1 www-data adm      1977 Apr 18 05:49 error.log.5.gz

Aquí está la misma información básica de registro dentro del contenedor de Discourse:

# cd /var/discourse/
# ./launcher enter socket
# cd /var/log/nginx
# ls -l
total 215440
-rw-r--r-- 1 www-data www-data  87002396 Jun 25 18:28 access.log
-rw-r--r-- 1 www-data www-data 101014650 Jun 25 08:02 access.log.1
-rw-r--r-- 1 www-data www-data   8217731 Jun 24 08:02 access.log.2.gz
-rw-r--r-- 1 www-data www-data   6972317 Jun 23 07:53 access.log.3.gz
-rw-r--r-- 1 www-data www-data   3136381 Jun 22 07:50 access.log.4.gz
-rw-r--r-- 1 www-data www-data   2661418 Jun 21 07:45 access.log.5.gz
-rw-r--r-- 1 www-data www-data   5098097 Jun 20 07:38 access.log.6.gz
-rw-r--r-- 1 www-data www-data   6461672 Jun 19 07:40 access.log.7.gz
-rw-r--r-- 1 www-data www-data         0 Jun 25 08:02 error.log
-rw-r--r-- 1 www-data www-data         0 Jun 24 08:02 error.log.1
-rw-r--r-- 1 www-data www-data        20 Jun 23 07:53 error.log.2.gz
-rw-r--r-- 1 www-data www-data       254 Jun 23 02:36 error.log.3.gz
-rw-r--r-- 1 www-data www-data        20 Jun 21 07:45 error.log.4.gz
-rw-r--r-- 1 www-data www-data        20 Jun 20 07:38 error.log.5.gz
-rw-r--r-- 1 www-data www-data        20 Jun 19 07:40 error.log.6.gz
-rw-r--r-- 1 www-data www-data       274 Jun 18 15:40 error.log.7.gz

Nota: Esa información “dentro del contenedor” también está disponible desde fuera del contenedor en el volumen compartido.

Por lo tanto (y para mantener esta respuesta breve), @ossia, casi todo lo que necesitas para obtener conocimiento situacional de lo que está sucediendo está en estos robustos archivos de registro. No es necesaria ninguna especulación. Los datos están todos ahí.

Incluso hay más datos excelentes disponibles en el registro de Rails, por ejemplo. En una de nuestras configuraciones, este es el registro de producción de Rails:

tail -f /var/discourse/shared/socket/log/rails/production.log

El registro de Rails también tiene mucha información de registro de usuarios, por ejemplo:

Started GET "/embed/comments?topic_id=378686" for 73.63.114.60 at 2020-06-25 18:36:15 +0000
Started GET "/embed/comments?topic_id=378686" for 195.184.106.202 at 2020-06-25 18:36:16 +0000
Started GET "/embed/comments?topic_id=378686" for 17.150.212.174 at 2020-06-25 18:36:16 +0000
Started GET "/embed/comments?topic_id=378686" for 76.235.99.73 at 2020-06-25 18:36:18 +0000
Started GET "/embed/comments?topic_id=378686" for 124.253.211.42 at 2020-06-25 18:36:19 +0000
Started GET "/embed/comments?topic_id=378686" for 103.96.30.11 at 2020-06-25 18:36:21 +0000
Started GET "/embed/comments?topic_id=378686" for 72.191.206.59 at 2020-06-25 18:36:22 +0000
Started GET "/embed/comments?topic_id=378686" for 68.252.68.76 at 2020-06-25 18:36:23 +0000
Started GET "/embed/comments?topic_id=378686" for 69.17.252.83 at 2020-06-25 18:36:23 +0000
Started GET "/embed/comments?topic_id=378686" for 98.109.33.230 at 2020-06-25 18:36:24 +0000

Nota: Aquí (arriba, como ejemplo) vemos las direcciones IP de los clientes que extraen el código incrustado de Discourse desde otro servidor.

La tarea en cuestión....

Volviendo a la tarea en cuestión, el “truco” es superar la especulación y las suposiciones, y realizar la divertida (1) filtración/limpieza de datos, (2) fusión de datos y (3) análisis de tus datos de sensores (archivos de registro) para crear (4) la conciencia situacional (SA) de lo que está sucediendo en tu sitio.

Para aplicaciones LAMP más antiguas, tengo código personalizado que escribí hace años, el cual escribe toda esta información en una tabla de base de datos y realiza el análisis en tiempo real, contando los “accesos” por dirección IP (como un ejemplo), donde puedo ver rápidamente qué, quién y desde dónde está accediendo al sitio, ya que se requiere cierto código para realizar este tipo de limpieza, filtrado y fusión de datos. (Útil durante ataques DDOS y actividad de bots maliciosos, por ejemplo).

Eso no es un problema para ti, @ossia, porque eres freeCodeCamp.org, por lo que tienes tanto el conocimiento para encontrar excelentes herramientas de análisis de archivos de registro (hay muchas en el ciberuniverso) y/o crear tu propio código personalizado para realizar el análisis de manera rápida y sencilla según el escenario que desees comprender (tu tema y problema).

Escribí mi código personalizado para una antigua aplicación LAMP heredada en unas pocas horas hace muchos años, y no soy un genio de la programación en absoluto, aunque a veces me llaman una “leyenda” en el campo de la ciberseguridad, LOL :slight_smile:

Para resumir....

Bueno, para resumir…

Tienes todos los datos necesarios para crear un conocimiento situacional profundo de “qué está pasando” en tu sitio, y puedes crear esa SA limpiando, filtrando, fusionando y realizando un análisis básico de los datos de tus archivos de registro. Existen herramientas que pueden ayudar, pero siempre encuentro más fácil escribir algo de código personalizado basado en el objetivo del análisis (análisis dependiente), YMMV. Pero puedes hacerlo fácilmente porque eres freeCodeCamp.org y tienes muchas habilidades técnicas.

Te recomiendo que te alejes de intentar obtener SA a partir de Google Analytics y otras aplicaciones de terceros basadas en JS. Nada es mejor que tus propios archivos de registro web (y datos de sesión de la base de datos si los tienes) y no tienes que preocuparte por “qué puede o no estar bloqueado”, etc. Tus archivos de registro del servidor web contienen los datos necesarios para obtener la CSA que necesitas (y también pueden personalizarse cuando sea necesario).

En parte de mi código de CSA, en realidad intercepto la información de sesión y la información de registro de las solicitudes HTTP que no registran nginx, apache2 y otros servidores web (para información adicional); pero aún no he escrito este tipo de código para Discourse, ya que no soy tan “fácil como el pastel” en el desarrollo de plugins de Discourse (como los gurús del equipo de meta Discourse aquí) como lo soy con aplicaciones LAMP, ya que solo comencé con Discourse hace unos meses y aún no he escrito ningún código personalizado de CSA para Discourse (y tratando de programar menos este año, para ser honesto).

La CSA se basa en la fusión de datos de sensores y, a partir de la CSA, surge el conocimiento necesario para comprender qué acciones debes tomar para remediar cualquier problema de ciberseguridad.

¡Mucha suerte en tu búsqueda y espero que esto te ayude a descansar más :slight_smile!

¡Saludos!


Referencia original (histórica) de CSA:

Referencia original (histórica) de CSA:

https://www.researchgate.net/publication/220420389_Intrusion_Detection_Systems_and_Multisensor_Data_Fusion

(Solo referencia para personas interesadas en los orígenes y la tecnología central de la CSA)

13 Me gusta

¡Gracias por su arduo trabajo, equipo!

& gracias muchísimo, muchísimo, muchísimo
por simplificar esa
barra de herramientas doble :nauseated_face:

4 Me gusta

Voy a cerrar el ciclo aquí.

Discourse ahora alberga https://forum.freecodecamp.org/. El sitio es extremadamente rápido y los scripts de spammers ya no causan ni un mínimo retraso. Seguimos sin tener muy claro cuál fue el problema en Digital Ocean; podría haber sido un vecino ruidoso, la máquina podría haber sido insuficiente o podría haber habido fallos técnicos. No estamos seguros. Pero el problema original ahora está resuelto al 100% y la comunidad está muy contenta.

16 Me gusta