O Discourse do freeCodeCamp.org está colapsando devido a scripts de spammers

Começamos a observar cargas elevadas no Discourse do freeCodeCamp.org em 21 de junho. Nossa carga média de CPU começou a aumentar a ponto de todo o fórum ficar sem resposta.

Nossa investigação inicial

  1. Atualizamos o Discourse da versão Estável para a versão mais recente de Testes Aprovados (Beta), que é recomendada para obter todas as correções de desempenho mais recentes. Não pareceu haver muita diferença.

  2. Removemos alguns dos plugins antigos para ajudar a diagnosticar o problema, e não parece ser o caso de lentidão.

  3. Para descartar qualquer configuração incorreta, testamos com o tema padrão e verificamos os /logs, onde encontramos algumas exceções:

    Job exception: could not obtain connection from the pool within 5.000 seconds (awaited 5.006 seconds); all pooled connections were in use

Isso parece ocorrer porque a instância já pode estar sobrecarregada com muitas tarefas de longa duração.

O contêiner do Discourse (upstream) está rodando na Digital Ocean e parece estar operando em alta temperatura em todos os 6 vCPUs. Atualmente, está na versão: 2.5.0.beta7

Investigação adicional

  1. Esta manhã, nossos moderadores nos informaram sobre um aumento significativo no número de contas de spam, o que nos levou a acreditar que poderia ser um ataque direcionado.

  2. Para tentar mitigar o problema, colocamos o fórum em modo somente leitura. Mesmo assim, a taxa de utilização de recursos permanece bastante alta, acima de 60%.

  3. Pelo que podemos observar: desde que isso começou, temos visto uma menor taxa de solicitações por segundo e uma maior taxa de solicitações atuais em nosso proxy:

  1. O volume de tráfego para The freeCodeCamp Forum - Join the developer community and learn to code for free. não mudou significativamente, de acordo com o Google Analytics.
  2. Nenhum dos outros aplicativos no mesmo proxy parece estar afetado. Nossas plataformas /news e /learn estão operando normalmente.
  3. Tentamos adicionar alguma limitação de taxa adicional no proxy, mas não fez muita diferença, então a removemos (para permitir que usuários normais continuem acessando nosso site).
  4. Também movemos os redirecionamentos do nosso antigo fórum.freecodecamp.com para fora do fórum .org, já que notamos que o antigo subdomínio (que não usamos há 2,5 anos) teve um grande pico de tráfego a partir de alguns dias atrás.

No momento, com aproximadamente 400 usuários em tempo real, as estatísticas são assim:

Observações sobre o comportamento do spammer

Os spammers parecem estar executando algum tipo de script que cria novas contas em uma instância do Discourse e, em seguida, aguarda vários dias. Depois, essas contas de repente se tornam ativas. Eles começam a postar novos tópicos com links para um site. (Talvez para criar backlinks?)

Algumas dessas contas foram criadas lá atrás em março.

Veja como é uma de suas postagens de spam, embora existam muitas variantes, linkando para vários sites:

Qualquer conselho será bem-vindo aqui.

14 curtidas

Isso merece muito mais atenção

Muito obrigado por compartilhar!

Uma das minhas instâncias favoritas do Discourse, a freeCodeCamp, parou nos últimos dois dias.

5 curtidas

Acho que o problema principal da alta carga não foi causado por spammers.
Eis o porquê:

O uso normal do Discourse (sem API) só funciona em navegadores modernos, pois depende fortemente de JavaScript. O Google Analytics também usa JavaScript para coletar várias informações do usuário (e não precisa de suporte moderno a JavaScript para executar o código de análise). Se o Google Analytics não consegue detectar a atividade do usuário, o Discourse também não deveria conseguir servir seu conteúdo e funcionalidades.

Aqui está uma captura de bot quando usei uma biblioteca antiga de navegador sem cabeça (phantomjs) para acessar o site do Discourse:

E aqui está com uma biblioteca mais moderna (puppeteer):

  1. É estranho se alguém conseguir postar respostas no Discourse (sem API) enquanto o Google Analytics não consegue detectá-los.
  2. Geralmente, spammers usam proxies públicos para fazer coisas ruins, e acho que seu Cloudflare é inteligente o suficiente para bloquear “visitantes ruins” antes que eles realmente alcancem seu aplicativo.

Você viu filas de jobs altas no processo Sidekiq?

2 curtidas

Isso ignora o fato de que mais da metade das pessoas em comunidades de tecnologia está executando alguma extensão de bloqueador de anúncios, que sempre bloqueia o Google Analytics por padrão.

18 curtidas

Incluindo o novo macOS.

Era apenas um boato desagradável. Veja abaixo.

5 curtidas

O Akismet está habilitado? Você pode ativar a moderação para o nível de confiança 1?

2 curtidas

Sim, vimos os jobs oscilando como parte da minha investigação inicial. Em seguida, procedemos à revogação de todas as chaves de usuário.

No entanto, isso não parece ter causado nenhuma mudança na estabilidade.

Embora estejamos trabalhando em estreita colaboração com a equipe do Discourse para resolver isso.

5 curtidas

Não vejo por que seria estranho se houver solicitações sem que análises baseadas em JavaScript mostrem nada.
O spammer pode usar os mesmos endpoints que o JavaScript usa, sem JavaScript. Nenhuma análise baseada em JavaScript seria acionada.

3 curtidas

Isso é muito mais provável de ser uma interação de plug-in ou uma configuração de proxy ruim. Não vemos nenhum sucesso de ‘spammers’ em nossa hospedagem.

Considerando que é um site de programação, também poderia ser ‘programadores’ tentando fazer algo estranho no fórum?

Mas não — spammers não são um problema generalizado entre as milhares de sites que hospedamos.

3 curtidas

Sim, acredito que seja algo relacionado à configuração (plugin ruim ou configuração de proxy) ou alguém mexendo no fórum.

O segundo caso é mais provável, considerando todos os padrões.

Pelo que observei, as contas de spam foram criadas ao longo de um longo período de tempo e estão tentando adicionar links (para obter backlinks??) em suas biografias e fazer todo tipo de coisa estranha.

Além disso, pode haver raspagem de dados envolvida, pois colocamos o site em modo somente leitura, configuramos um cache no proxy e também aplicamos limitação de taxa. O uso de recursos parece estar alto, independentemente do container upstream.

No entanto, não descartaria que nossa configuração também possa estar ruim. Usamos subcaminhos e o Cloudflare em cima do nosso proxy reverso, o que tradicionalmente não é a configuração mais eficiente recomendada pelo Discourse.

1 curtida

image

42,5% de roubo de CPU é realmente alto, mesmo que você seja quem está causando os problemas naquele hipervisor. Isso parece um vizinho barulhento para mim. Se eu fosse você, entraria em contato com a DigitalOcean e pediria para mover o droplet para outro hipervisor.

10 curtidas

Tenho certeza de que você provavelmente já está fazendo isso, mas só para ter certeza, sugiro monitorar as conexões TCP/UDP abertas resumidas por IP no nível do sistema operacional. Se houver alta carga de CPU, isso deve mostrar uma quantidade massiva de conexões abertas para o servidor web.

Algum padrão estranho no production.log?

1 curtida
4 curtidas

Olá Quincy @ossia

Vamos dar um passo atrás por um momento e analisar isso de uma perspectiva profissional de cibersegurança, sem especulações e sem essa abordagem de “agarrar-se a palhas”.

O conceito-chave em todas as tarefas de cibersegurança é a “consciência situacional”; neste caso, chamamos de “consciência situacional cibernética” (CSA).

Para saber “o que está acontecendo” de forma definitiva, você precisa desenvolver o melhor conhecimento situacional possível, sem especulações ou palpites. Apenas os fatos.

Como fazer isso?

Bem, muito brevemente:

Bem, muito brevemente:

Fazemos isso fundindo informações de todos os nossos sensores. Para aplicações baseadas na web, isso normalmente vem dos arquivos de log e dos dados de sessão. Não creio (de cabeça) que o Discourse mantenha informações de sessão no banco de dados PG (da última vez que verifiquei, não havia uma tabela de sessão como em algumas aplicações web LAMP), mas isso não é um impeditivo absoluto.

Você tem quase tudo o que precisa nos arquivos nginx log files tanto para o seu proxy reverso fora do contêiner (lembro-me de ter lido neste tópico que você estava usando nginx como proxy) quanto a mesma informação de registro está dentro do contêiner. Em ambas as configurações, o arquivo de log está aqui, na configuração padrão OOTB:

Aqui está um exemplo em uma de nossas configurações (fora do contêiner) para o proxy reverso:

# 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

Aqui estão as mesmas informações básicas de registro dentro do contêiner do 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: Essas informações “dentro do contêiner” também estão disponíveis fora do contêiner, no volume compartilhado.

Portanto (e para manter esta resposta curta), @ossia, quase tudo o que você precisa para obter conhecimento situacional do que está acontecendo está nesses robustos arquivos de log. Nenhuma especulação é necessária. Os dados estão todos lá.

Há ainda mais dados excelentes disponíveis no log do Rails, por exemplo. Em uma de nossas configurações, aqui está o log de produção do Rails:

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

O log do Rails também tem muitas informações de registro de usuários excelentes, por exemplo:

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: Aqui (acima, como exemplo), vemos os endereços IP de clientes buscando o código incorporado do Discourse de outro servidor.

A tarefa em questão....

Voltando à tarefa em questão, o “truque” é ir além da especulação e dos palpites e fazer a (1) filtragem/limpeza de dados, (2) fusão de dados e (3) análise dos dados dos seus sensores (arquivos de log) para criar (4) a consciência situacional (SA) do que está acontecendo no seu site.

Para aplicações LAMP mais antigas, tenho na verdade um código personalizado que escrevi há anos, que grava todas essas informações em uma tabela de banco de dados e faz a análise em tempo real, contando os “acessos” por endereço IP (como um exemplo), onde posso ver rapidamente o quê, quem e de onde está acessando o site, pois é necessário algum código para fazer essa limpeza, filtragem e fusão de dados. (Útil durante ataques DDoS e atividade de bots maliciosos, por exemplo).

Isso não é problema para você, @ossia, pois você é freeCodeCamp.org, então tem tanto o conhecimento para encontrar ótimas ferramentas de análise de arquivos de log (existem muitas no ciberuniverso) quanto/ou criar seu próprio código personalizado para fazer a análise de forma rápida e fácil com base no cenário que deseja entender (seu tópico e problema).

Escrevi meu código personalizado para uma antiga aplicação LAMP legada em poucas horas, há muitos anos, e não sou de forma alguma um gênio da programação, mesmo sendo às vezes chamado de “lenda” por muitos na área de cibersegurança, LOL :slight_smile:

Para resumir....

Bem, para resumir…

Você tem todos os dados necessários para criar um conhecimento situacional profundo do “que está acontecendo” no seu site. E você pode criar essa SA limpando, filtrando, fundindo e fazendo uma análise básica dos dados dos seus arquivos de log. Existem ferramentas que podem ajudar, mas sempre acho mais fácil escrever um código personalizado baseado no objetivo da análise (análise dependente), YMMV. Mas você pode fazer isso facilmente porque é freeCodeCamp.org e tem muitas habilidades técnicas.

Recomendo que você se afaste de tentar obter SA por meio do Google Analytics e de outros aplicativos de terceiros baseados em JS. Nada é melhor do que seus próprios arquivos de log da web (e dados de sessão do banco de dados, se você os tiver), e você não precisa se preocupar com “o que pode ou não estar bloqueado” etc. Seus arquivos de log do servidor web contêm os dados para obter a CSA de que você precisa (e também podem ser personalizados quando necessário).

Em alguns dos meus códigos de CSA, na verdade intercepto as informações de sessão e de registro das solicitações HTTP não registradas pelo nginx, apache2 e outros servidores web (para informações adicionais); mas ainda não escrevi esse tipo de código para o Discourse (ainda), pois não sou um desenvolvedor de plugins do Discourse “tão fácil quanto bolo” (como os gurus da equipe do Meta Discourse aqui) quanto sou com aplicações LAMP, tendo começado com o Discourse há apenas alguns meses e ainda não escrevi nenhum código personalizado de CSA para o Discourse (e tentando codificar menos este ano, para ser honesto).

A CSA é baseada na fusão de dados de sensores e, a partir dela, surge o conhecimento para entender quais ações você precisa tomar para remediar qualquer problema de cibersegurança.

Tudo de melhor na sua busca e espero que isso ajude você a descansar mais :slight_smile:

Abraços!


Referência Original (Histórica) de CSA:

Referência Original (Histórica) de CSA:

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

(Referência apenas para pessoas interessadas nas origens e na tecnologia central da CSA)

13 curtidas

Obrigado pelo seu trabalho árduo, equipe!

& muito, muito, muito obrigado
por simplificar aquela
_barra de ferramentas :nauseated_face:

4 curtidas

Vou apenas fechar o ciclo aqui.

O Discourse agora está hospedando https://forum.freecodecamp.org/. O site é extremamente rápido e os scripts de spam não estão mais causando nenhum problema. Ainda não temos certeza sobre qual era o problema na Digital Ocean: pode ter havido um “vizinho barulhento”, a máquina pode ter sido subdimensionada ou pode ter havido algum problema técnico; não sabemos ao certo. Mas o problema original agora está 100% resolvido e a comunidade está muito feliz.

16 curtidas