freeCodeCamp.org Discourse がスパムスクリプトで崩壊中

6 月 21 日、freeCodeCamp.org の Discourse 上で負荷の急増を観測しました。平均 CPU 負荷が上昇し、最終的にフォーラム全体が応答しなくなる状態に陥りました。

初期調査

  1. Discourse を安定版から最新のパス済みテスト版(ベータ)に更新しました。これは最新の性能改善を取り入れるために推奨される手順です。しかし、大きな変化は見られませんでした。

  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. 判明した限りでは、この問題が発生して以来、プロキシ上で 1 秒あたりのリクエスト数が減少し、同時進行中のリクエスト数が増加しています:

  1. Google アナリティクスによると、The freeCodeCamp Forum - Join the developer community and learn to code for free. へのトラフィック量に大きな変化はありません。
  2. 同じプロキシ上で動作している他のアプリケーションには影響が出ていません。/news および /learn プラットフォームは通常通り動作しています。
  3. プロキシ上で追加のレート制限を試しましたが、効果はほとんどなかったため、それを解除しました(通常ユーザーがサイトにアクセスできるようにするため)。
  4. また、2 年半以上使用されていなかった旧サブドメイン(forum.freecodecamp.com)から数日前にトラフィックの急増が見られたため、.org フォーラムへのリダイレクトを旧サブドメインから外しました。

現在、リアルタイムユーザーが約 400 人の状況での統計は以下の通りです:

スパマーの行動に関する観察

スパマーは、Discourse インスタンスに新しいアカウントを作成するスクリプトを実行し、数日間待機してから、それらのアカウントを突然アクティブにする手法を用いているようです。その後、新しいスレッドを作成し、特定のウェブサイトへのリンクを投稿します(おそらくバックリンク構築のためでしょうか)。

これらのアカウントの中には、3 月に作成されたものもあります。

以下は、彼らのスパム投稿の一例です(多くのバリエーションがあり、多数のウェブサイトへのリンクが含まれています):

ご助言をいただければ幸いです。

「いいね!」 14

これはもっと注目されるべきです

共有してくださり、ありがとうございます!

私の好きな Discourse インスタンスの一つである freeCodeCamp が、ここ 2 日間、完全に停止してしまいました。

「いいね!」 5

スパマーが重い負荷の主な原因だとは思いません。
その理由は以下の通りです:

通常のディスコースの利用(API を使わない場合)は、JavaScript に強く依存しているため、最新のブラウザでしか動作しません。Google アナリティクスもユーザーのさまざまな情報を収集するために JavaScript を使用していますが、分析コードを実行するために最新の JavaScript サポートは必要ありません。Google アナリティクスがユーザーのアクティビティを検出できない場合、ディスコースもコンテンツや機能を提供できないはずです。

以下は、古いヘッドレスブラウザライブラリ(PhantomJS)を使用してディスコースサイトにアクセスした際のボットのキャプチャです:

そして、より現代的なライブラリ(Puppeteer)を使用した場合はこちらです:

  1. Google アナリティクスがユーザーを検出できないのに、誰かがディスコースで(API を使わずに)返信を投稿できるのは不思議です。
  2. 通常、スパマーは不正な行為を行うために公開プロキシを使用しますが、あなたの Cloudflare はアプリに実際に到達する前に*「悪い訪問者」*を阻止するのに十分賢いはずです。

Sidekiq プロセスでキューのジョブが大量に溜まっているのをご覧になりましたか?

「いいね!」 2

これは、技術コミュニティの人の半数以上が何らかの広告ブロッカー拡張機能を実行しており、それらがデフォルトで常に Google アナリティクスをブロックするという事実を無視しています。

「いいね!」 18

新しい macOS も含めて。

ただの悪質な噂でした。以下をご覧ください。

「いいね!」 5

Akismet は有効になっていますか?信頼レベル 1 のモデレーションを有効にできますか?

「いいね!」 2

はい、初期調査の一環として、ジョブが上下に揺れているのを確認しました。その後、すべてのユーザーキーを無効化しました。

ただし、安定性への影響は特に見られませんでした。

現在、Discourse チームと緊密に連携し、この問題の解決に取り組んでいます。

「いいね!」 5

JavaScript ベースの分析ツールが何も表示しなくても、リクエストが存在することに不思議な点はないと思います。
スパマーは JavaScript を使わずに、JavaScript が使用するのと同じエンドポイントを利用できます。そのため、JavaScript ベースの分析ツールはトリガーされません。

「いいね!」 3

これは、プラグインの相互作用か、プロキシ設定の不備である可能性が非常に高いです。当社のホスティングでは、スパマーからの成功例は皆無です。

コーディングサイトであることから、フォーラムで何か奇妙なことをしようとする「コーダー」がいる可能性もあるでしょうか?

しかし、いいえ——スパマーは、当社のホストする数千のサイト全体で一般的な問題ではありません。

「いいね!」 3

はい、設定(悪いプラグインやプロキシ設定)か、誰かがフォーラムをいじっているかのどちらかだと考えられます。

パターンから判断すると、後者の可能性の方が高いです。

私が気づいたのは、スパムアカウントは長い期間にわたって作成されており、バイオやその他様々な箇所にリンク(バックリンク獲得のためでしょうか?)を追加しようとしている点です。

また、サイトを読み取り専用モードにし、プロキシ上にキャッシュを設定するとともにレート制限も導入しているにもかかわらず、アップストリームコンテナのリソース使用量が高いことから、スクレイピングが関与している可能性もあります。

ただし、設定に問題がある可能性も否定できません。サブパスを使用し、リバースプロキシの上に Cloudflare を配置しているため、Discourse が推奨する最も効率的な構成とは言い難いからです。

「いいね!」 1

image

42.5% のステールは非常に高い値です。仮にそのハイパーバイザー上の問題を引き起こしているのがあなた自身だとしてもです。これは「騒がしい隣人」のように見えます。私なら DigitalOcean に連絡し、ドロプレットを別のハイパーバイザーに移転するよう依頼します。

「いいね!」 10

おそらく既に行っているかと思いますが、念のため、OS レベルで IP ごとに集計されたオープンな TCP/UDP 接続を監視することをお勧めします。CPU 負荷が高い場合、ウェブサーバーへの大量のオープン接続が表示されるはずです。

production.log に異常なパターンはありますか?

「いいね!」 1
「いいね!」 4

こんにちは、Quincy @ossia さん。

少し立ち止まって、推測や「藁にもすがる」的なアプローチを排し、プロのサイバーセキュリティの視点からこの問題を俯瞰してみましょう。

すべてのサイバーセキュリティタスクにおける鍵となる概念は「状況認識(Situational Awareness)」です。この文脈では「サイバー状況認識(CSA: Cyber Situational Awareness)」と呼ばれます。

「何が起きているか」を推測や憶測ではなく、明確に把握するためには、推測を排した最良の状況知識を構築する必要があります。事実のみです。

では、どうすればよいのでしょうか?

ええと、ごく簡潔に言うと:

ええと、ごく簡潔に言うと:

これは、すべてのセンサーからの情報を融合させることで行います。Web ベースのアプリケーションの場合、通常はログファイルとセッションデータから取得されます。Discourse が PG データベースにセッション情報を保持しているかどうかは(私の記憶が正しければ、いくつかの LAMP Web アプリにあるようなセッションテーブルは存在しなかったはずです)、私の頭の中ではっきりとはわかりませんが、それは致命的な問題ではありません。

コンテナ外のリバースプロキシ(このトピックで nginx をプロキシとして使用していると読んだ記憶があります)と、コンテナ内でも、両方の nginx ログファイル に必要な情報のほとんどが揃っています。どちらの構成でも、標準の OOTB(Out of the Box)設定ではログファイルは以下の場所にあります:

ここは、リバースプロキシの設定例(コンテナ外)です:

# 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 の production ログは以下のようになります:

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 アプリの場合、私は何年も前に作成したカスタムコードを持っており、これらすべての情報を DB テーブルに書き込み、リアルタイムで分析を実行し、IP アドレスごとの「ヒット」数をカウントしています(その一例として)。これにより、誰が、どこから、何をサイトにアクセスしているかを素早く確認できます。このようなデータのクリーニング、フィルタリング、融合にはある程度のコードが必要だからです(DDoS 攻撃や不正なボットの活動などに役立ちます)。

@ossia さんにとっては問題ありません。あなたは freeCodeCamp.org のメンバーですので、優れたログファイル分析ツールを見つける知識(サイバースペースには多数存在します)と、またはシナリオに応じて分析を迅速かつ簡単に行うための独自のカスタムコードを作成する技術の両方を備えています。

私は数年前、古いレガシーな LAMP アプリのために数時間でこのカスタムコードを作成しましたが、サイバーセキュリティの分野では「伝説」と呼ばれることもありますが、決して コーディングの天才 ではありません(笑):slight_smile:

まとめ....

ええと、まとめますと…

サイト上で「何が起きているか」についての深い状況知識を構築するために必要なデータはすべて揃っています。ログファイルデータをクリーニング、フィルタリング、融合し、基本的な分析を行うことで、その SA を作成できます。これを支援するツールも存在しますが、私は分析の目的(依存分析)に基づいてカスタムコードを素早く作成する方が簡単だと常に感じています。YMMV(人それぞれですが)、あなたは freeCodeCamp.org のメンバーであり、多くの技術スキルを持っているため、これを簡単に行うことができます。

Google アナリティクスや他の JS ベースのサードパーティ製アプリから SA を得ようとすることはやめることをお勧めします。何よりも優れているのは、独自の Web ログファイル(および DB セッションデータがあればそれ)であり、「ブロックされるかどうか」などを気にする必要もありません。Web サーバーのログファイルには、必要な CSA を得るためのデータが含まれており、必要に応じてカスタマイズすることも可能です。

私の CSA コードの一部では、nginx、apache2、その他の Web サーバーがログに記録していない HTTP リクエストからのセッション情報やログ情報を実際にインターセプトして記録しています(追加情報として)。しかし、Discourse 向けのこのようなコードは(まだ)書いていません。なぜなら、私は LAMP アプリほど 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

チームの皆さんのご尽力に感謝します!

そして、あの吐き気を催すようなツールバーを簡素化してくださって、本当に本当に本当にありがとうございます。

「いいね!」 4

ここで最終的な確認をさせていただきます。

Discourse は現在、https://forum.freecodecamp.org/ をホストしています。サイトは非常に高速で、スパムスクリプトが原因でわずかな遅延さえ発生しなくなりました。Digital Ocean でどのような問題が発生していたかについては、まだ完全には明確になっていません。隣接するサーバーからの干渉(ノイズ)があった可能性、マシンが性能不足だった可能性、あるいは機械的な不具合があった可能性など、いくつかの要因が考えられますが、現時点では確実なことはわかりません。しかし、元の問題は完全に解決され、コミュニティは非常に満足しています。

「いいね!」 16