freeCodeCamp.org Discourse 正因垃圾邮件脚本而崩溃

我们从 6 月 21 日开始注意到 freeCodeCamp.org 的 Discourse 论坛负载显著升高。我们的平均 CPU 负载开始增长,最终导致整个论坛无法响应。

初步调查

  1. 我们将 Discourse 从稳定版更新到了最新的“测试通过”(Beta)版本,该版本推荐用于获取所有最新的性能修复。但似乎并没有太大改善。

  2. 我们移除了部分旧插件以帮助诊断问题,但似乎并非这些插件导致性能下降。

  3. 为了排除配置错误,我们使用默认主题进行了测试,并检查了 /logs,确实发现了一些异常:

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

这似乎是因为实例可能已经因大量长时间运行的任务而处于高负载状态。

Discourse 容器(上游)运行在 Digital Ocean 上,所有 6 个 vCPU 似乎都处于高负载状态。当前运行版本为:2.5.0.beta7

进一步调查

  1. 今天早上,我们的版主告知我们,垃圾账号数量显著增加,这让我们怀疑这可能是一次针对性攻击。

  2. 为缓解问题,我们将论坛设置为只读模式。尽管如此,资源利用率仍然很高,达到 60% 以上。

  3. 根据我们的观察:自问题开始以来,我们的代理服务器上的每秒请求数(requests-per-second)降低,而当前请求率升高:

  1. 根据 Google Analytics 的数据,访问 The freeCodeCamp Forum - Join the developer community and learn to code for free. 的流量并未发生显著变化。
  2. 同一代理上的其他应用程序似乎未受影响。我们的 /news 和 /learn 平台运行正常。
  3. 我们尝试在代理上增加一些额外的速率限制,但效果不明显,因此我们将其移除(以便正常用户能够继续访问我们的网站)。
  4. 我们还注意到旧子域名 forum.freecodecamp.com(我们已有 2.5 年未使用)在几天前出现流量激增,因此已将相关重定向从 .org 论坛移开。

目前,在约 400 名实时用户的情况下,统计数据如下:

关于垃圾发送者行为的观察

垃圾发送者似乎运行某种脚本,在 Discourse 实例上创建新账号,然后等待数天。之后这些账号突然变得活跃,开始发布包含指向某些网站链接的新主题。(可能是为了构建反向链接?)

其中一些账号早在三月份就已创建。

以下是其中一条垃圾帖子的示例,尽管存在许多变体,并链接到多个网站:

在此欢迎任何建议。

14 个赞

这值得更多关注

非常感谢你的分享!

我最喜欢的 Discourse 实例之一 freeCodeCamp,过去两天几乎完全停摆。

5 个赞

我认为负载过重的主要问题并非由垃圾邮件发送者引起。
理由如下:

正常的 Discourse 使用(不通过 API)必须依赖现代浏览器,因为它高度依赖 JavaScript。Google Analytics 同样使用 JavaScript 来收集各种用户信息(而且运行分析代码并不需要现代 JavaScript 支持)。如果 Google Analytics 无法检测到用户活动,那么 Discourse 理应也无法提供其内容和功能。

以下是我使用旧版无头浏览器库(phantomjs)访问 Discourse 站点时的机器人捕获结果:

而使用更现代的库(puppeteer)的结果如下:

  1. 如果有人能够在不使用 API 的情况下在 Discourse 上发布回复,而 Google Analytics 却无法检测到他们,这实在令人费解。
  2. 通常,垃圾邮件发送者会使用公开代理进行恶意操作,而我认为您的 Cloudflare 足够智能,能够在这些“不良访客”真正触及您的应用之前就将其拦截。

您是否观察到 Sidekiq 进程中有大量排队任务?

2 个赞

这忽略了一个事实:超过一半的科技社区用户都安装了某种广告拦截扩展,这些扩展默认会屏蔽 Google Analytics。

18 个赞

包括 新的 macOS。

这只是一个恶意的谣言。参见 下文

5 个赞

Akismet 已启用吗?您能为信任等级 1 开启审核功能吗?

2 个赞

是的,我们在初步调查过程中确实观察到任务队列的波动。随后,我们撤销了所有用户密钥。

不过,这似乎并未对稳定性产生任何改变。

尽管如此,我们正与 Discourse 团队紧密合作,以解决此问题。

5 个赞

我不明白为什么在没有基于 JavaScript 的分析工具显示任何内容的情况下出现请求会显得奇怪。
垃圾邮件发送者可以在不使用 JavaScript 的情况下,利用与 JavaScript 相同的端点。因此,基于 JavaScript 的分析工具不会触发。

3 个赞

这更可能是插件交互问题或代理配置不当。在我们的托管服务中,我们从未见过“垃圾邮件发送者”取得成功。

鉴于这是一个编程网站,也可能是“程序员”试图在论坛上搞些奇怪的事情?

但事实并非如此——垃圾邮件发送者并不是我们托管的数千个网站上普遍存在的问题。

3 个赞

是的,我倾向于认为问题出在配置(插件配置不当或代理设置问题)或有人恶意篡改论坛。从各种模式来看,后者更可能是原因。

据我观察,这些垃圾账号是在很长一段时间内创建的,它们试图在个人简介中添加链接(以获取反向链接?),并表现出各种奇怪的行为。

此外,可能还存在爬虫抓取行为,因为我们将网站设置为只读模式,并在代理上配置了缓存和速率限制。尽管如此,上游容器的资源使用率似乎仍然很高。

不过,我们也不能完全排除自身配置存在问题的可能性。我们使用了子路径,并在反向代理之上部署了 Cloudflare,而按照 Discourse 的传统建议,这并非最高效的配置方案。

1 个赞

image

42.5% 的偷取率确实很高,即使问题正是由你所在的 hypervisor 引起的。在我看来,这就像是一个“吵闹的邻居”。如果你是我,我会联系 DigitalOcean,请求他们将 Droplet 迁移到另一个 hypervisor 上。

10 个赞

我相信你可能已经做了这些,但为了保险起见,我建议你在操作系统层面监控按 IP 汇总的开放 TCP/UDP 连接。如果 CPU 负载很高,应该会显示大量指向 Web 服务器的开放连接。

production.log 中是否有任何异常模式?

1 个赞
4 个赞

你好 Quincy @ossia

让我们先退一步,从专业网络安全的角度,抛开猜测和“病急乱投医”的方式来审视这个问题。

所有网络安全任务的核心概念是“态势感知”(situational awareness),在本例中称为“网络态势感知”(CSA)。

为了以确定的方式了解“正在发生什么”,你需要在不做任何猜测或臆测的情况下,尽可能建立最完善的态势认知——只关注事实。

具体该怎么做呢?

嗯,简要说明如下:

嗯,简要说明如下:

我们通过融合来自所有传感器的信息来实现这一点;对于基于 Web 的应用程序,这些信息通常来自日志文件和会话数据。我一时想不起来(off the top of my head)Discourse 是否在 PostgreSQL 数据库中维护会话信息(上次我检查时,不像某些 LAMP Web 应用那样有专门的会话表),但这完全不是问题。

你所需的大部分信息都包含在 nginx 日志文件 中,无论是容器外部的反向代理(我记得在这个话题中你提到过使用 nginx 作为代理),还是容器内部,都有相同的日志信息。在两种设置中,日志文件都位于标准开箱即用(OOTB)配置下的相同位置:

以下是我们某个设置中(容器外部)反向代理的示例:

# 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

以下是 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

注意:上述“容器内”的信息也可以通过共享卷从容器外部访问。

因此(为保持回复简洁),@ossia,你几乎可以从这些健壮的日志文件中获取了解“正在发生什么”所需的所有态势认知。无需任何猜测,数据就在那里。

此外,在 Rails 日志中还有更多有价值的数据。例如,在我们某个设置中,Rails 生产日志如下:

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

Rails 日志也包含大量优秀的用户日志信息,例如:

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

注意:以上示例中,我们看到了从另一台服务器拉取 Discourse 嵌入代码的客户端 IP 地址。

当前任务....

回到当前任务,关键在于超越猜测和臆断,进行有趣的(1)过滤/数据清洗、(2)数据融合,以及(3)对传感器数据(日志文件)的分析,从而构建(4)关于你网站当前状况的态势感知(SA)。

对于较旧的 LAMP 应用,我多年前编写了自定义代码,将所有这些信息写入数据库表,并实时进行分析,按 IP 地址统计“访问次数”(仅为示例之一),这样我就能快速了解谁、从何处、正在访问网站的哪些内容。因为要进行此类数据清洗、过滤和融合,确实需要一些代码。(例如在 DDoS 攻击和恶意机器人活动时非常有用)。

这对您来说 @ossia 完全不是问题,因为您是 freeCodeCamp.org,具备寻找优秀日志分析工具的知识(网络空间中有许多此类工具),或者可以根据您希望理解的具体场景(您的话题和问题)快速轻松地创建自己的自定义代码进行分析。

多年前,我为一个旧的遗留 LAMP 应用编写自定义代码仅用了几个小时,而我绝非任何意义上的 编程天才,尽管在网络安全领域,我有时被许多人称为“传奇”,哈哈 :slight_smile:

总结一下....

好吧,总结一下……

你拥有所有必要的数据,可以深入掌握“网站上正在发生什么”的态势认知。你可以通过清洗、过滤、融合日志数据并进行一些基本分析来构建这种态势感知(SA)。虽然有一些工具可以提供帮助,但我发现根据分析目标(依赖分析)快速编写一些自定义代码往往更简单,当然这因人而异(YMMV)。但你完全可以做到,因为你是 freeCodeCamp.org,拥有强大的技术技能。

我建议你彻底放弃试图通过 Google Analytics 和其他基于 JavaScript 的第三方应用来获取态势感知。没有什么比你自己的 Web 日志文件(以及如果有会话数据的话,数据库会话数据)更好的了,你也不必担心“某些内容可能被或未被阻止”等问题。你的 Web 服务器日志文件包含了获取所需 CSA 的数据(并且必要时还可以进行自定义)。

在我的一些 CSA 代码中,我实际上会拦截那些未被 nginx、apache2 和其他 Web 服务器记录的 HTTP 请求中的会话信息和日志信息(以获取额外信息);但我尚未为 Discourse 编写此类代码,因为作为 Discourse 插件开发者,我远不如这里的 meta Discourse 团队专家那样“轻车熟路”。我接触 Discourse 才几个月,尚未为 Discourse 编写任何自定义 CSA 代码(老实说,今年我尽量少写代码)。

CSA 基于传感器数据的融合,而从 CSA 中获得的认知将指导你采取哪些行动来解决任何网络安全问题。

祝你在探索中一切顺利,希望这能帮助你获得更充足的休息 :slight_smile

祝好!


原始(历史)CSA 参考文献:

原始(历史)CSA 参考文献:

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

(仅供对 CSA 起源和核心技术感兴趣的人士参考)

13 个赞

感谢大家的辛勤工作,团队!

& 非常非常非常感谢
你们简化了那个
令人作呕的工具栏 :nauseated_face:

4 个赞

我这就把这件事做个了结。

Discourse 现在托管着 https://forum.freecodecamp.org/。该网站运行极其流畅,垃圾脚本不再造成任何干扰。关于之前在 DigitalOcean 上遇到的问题,我们仍不太清楚具体原因:可能是“吵闹的邻居”效应,机器性能不足,也可能是机器本身出现了故障,目前尚不确定。但无论如何,原始问题现已彻底解决,社区成员对此感到非常满意。

16 个赞