Discourse freeCodeCamp.org рушится из-за скриптов спамеров

Мы начали замечать повышенную нагрузку на Discourse freeCodeCamp.org 21 июня. Средняя загрузка процессора начала расти до тех пор, пока весь форум не перестал отвечать.

Наше первоначальное расследование

  1. Мы обновили Discourse со стабильной версии до последней версии Tests-Passed (бета), что рекомендуется для получения всех последних исправлений производительности. Однако значительной разницы не наблюдалось.

  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 виртуальных процессоров. В настоящее время он работает на версии: 2.5.0.beta7

Дальнейшее расследование

  1. Сегодня утром наши модераторы сообщили о значительном увеличении количества спам-аккаунтов, что заставило нас предположить, что это может быть целенаправленная атака.

  2. Чтобы попытаться смягчить проблему, мы перевели форум в режим только для чтения. Несмотря на это, уровень использования ресурсов остается довольно высоким — более 60%.

  3. Судя по нашим данным: с момента начала проблемы мы наблюдаем снижение количества запросов в секунду и увеличение текущего количества запросов на нашем прокси:

  1. Объем трафика на The freeCodeCamp Forum - Join the developer community and learn to code for free., согласно Google Analytics, существенно не изменился.
  2. Ни одно из других приложений на том же прокси не пострадало. Наши платформы /news и /learn работают нормально.
  3. Мы попробовали добавить дополнительное ограничение скорости на прокси, но это мало что изменило, поэтому мы его убрали (чтобы обычные пользователи могли продолжать посещать наш сайт).
  4. Мы также перенаправили старые редиректы с форума.freecodecamp.com с форума .org, так как заметили, что на старый поддомен (который мы не использовали уже 2,5 года) несколько дней назад начался резкий скачок трафика.

На данный момент при примерно 400 активных пользователей статистика выглядит следующим образом:

Наблюдения за поведением спамеров

Спамеры, похоже, используют какой-то скрипт, который создает новые аккаунты на экземпляре Discourse, а затем ждет несколько дней. После этого эти аккаунты внезапно становятся активными. Они начинают публиковать новые темы со ссылками на веб-сайты. (Возможно, для создания обратных ссылок?)

Некоторые из этих аккаунтов были созданы еще в марте.

Вот как выглядит одно из их спам-сообщений, хотя существует множество вариантов со ссылками на разные сайты:

Любые советы будут приветствоваться.

Это заслуживает гораздо большего внимания

Спасибо большое за то, что поделились!

Один из моих любимых экземпляров Discourse, freeCodeCamp, последние два дня практически остановился.

Я не думаю, что основная проблема с высокой нагрузкой вызвана спамерами.
Вот почему:

Обычное использование Discourse (без API) возможно только в современных браузерах, так как оно сильно зависит от JavaScript. Google Analytics также использует JavaScript для сбора различных данных о пользователях (и не требует поддержки современного JavaScript для выполнения кода аналитики). Если Google Analytics не может отслеживать активность пользователей, то и Discourse не должен быть способен предоставлять свой контент и функции.

Вот пример перехвата бота при использовании старой библиотеки для головных браузеров (phantomjs) для доступа к сайту Discourse:

А вот с более современной библиотекой (puppeteer):

  1. Странно, если кто-то может публиковать ответы на Discourse (без API), в то время как Google Analytics не может их обнаружить.
  2. Обычно спамеры используют публичные прокси для совершения грязных дел, и я думаю, что ваш Cloudflare достаточно умен, чтобы блокировать “плохих посетителей” еще до того, как они доберутся до вашего приложения.

Вы видели высокую очередь задач в процессе Sidekiq?

Это игнорирует тот факт, что более половины людей в технических сообществах используют какое-либо расширение для блокировки рекламы, которое по умолчанию всегда блокирует Google Analytics.

Включая новую macOS.

Это был лишь неприятный слух. См. ниже.

Включён ли Akismet? Можно ли включить модерацию для уровня доверия 1?

Да, мы видели, как задачи то появлялись, то исчезали в ходе моего первоначального расследования. После этого мы отозвали все ключи пользователей.

Однако это, похоже, не повлияло на стабильность.

Тем не менее мы тесно сотрудничаем с командой Discourse для решения этой проблемы.

Я не вижу ничего странного в том, что могут быть запросы, которые не отображаются в аналитике на основе JavaScript.
Спаммер может использовать те же конечные точки, что и JavaScript, но без самого JavaScript. Аналитика на основе JavaScript не сработает.

Скорее всего, это связано с взаимодействием плагинов или неверной настройкой прокси. Со стороны «спаммеров» на нашем хостинге мы не фиксируем ни одного успешного случая.

Учитывая, что это сайт для программистов, возможно, это «кодеры», пытающиеся что-то странное сделать на форуме?

Но нет — спам не является общей проблемой для тысяч сайтов, которые мы хостим.

Да, я склоняюсь к тому, что проблема либо в конфигурации (плохой плагин или настройка прокси), либо в том, что кто-то намеренно портит форум.

Второй вариант более вероятен, судя по всем наблюдаемым паттернам.

Судя по моим наблюдениям, спам-аккаунты создавались в течение длительного времени, и они пытаются добавлять ссылки (чтобы получить обратные ссылки??) в свои биографии и делают всякие странные вещи.

Также возможно участие скраперов, поскольку мы перевели сайт в режим только для чтения, настроили кэширование на прокси, а также ограничили частоту запросов. Тем не менее, потребление ресурсов остается высоким на стороне контейнера-источника.

Однако я не исключаю, что наша конфигурация также может быть некорректной: мы используем субпути и Cloudflare поверх нашего обратного прокси, что традиционно не является наиболее эффективной настройкой, рекомендуемой для Discourse.

image

42,5% кражи времени процессора — это действительно много, даже если вы сами создаете нагрузку на этом гипервизоре. По мне, так это похоже на «шумного соседа». Если бы я был на вашем месте, я бы связался с DO и попросил переместить дроплет на другой гипервизор.

Скорее всего, вы уже это делаете, но на всякий случай рекомендую отслеживать открытые TCP/UDP-соединения, сгруппированные по IP, на уровне операционной системы. Если наблюдается высокая загрузка ЦП, это должно показать огромное количество открытых соединений к веб-серверу.

Есть ли какие-либо необычные паттерны в production.log?

Привет, Квинси @ossia

Давайте на секунцу отступим назад и посмотрим на это с профессиональной точки зрения кибербезопасности, без спекуляций и попыток «хвататься за соломинку».

Ключевое понятие во всех задачах кибербезопасности — это «осведомлённость о ситуации» (situational awareness), поэтому в данном случае речь идёт о «киберосведомлённости о ситуации» (CSA — Cyber Situational Awareness).

Чтобы точно знать, «что происходит», необходимо сформировать максимально полную картину ситуации, опираясь только на факты, без догадок и предположений.

Как это сделать?

Ну, очень кратко:

Ну, очень кратко:

Мы делаем это, объединяя информацию со всех наших датчиков. Для веб-приложений эта информация обычно поступает из файлов логов и данных сессий. Насколько я помню (без проверки), Discourse не хранит информацию о сессиях в базе данных PostgreSQL (в последний раз, когда я проверял, там не было таблицы сессий, как в некоторых LAMP-приложениях), но это совсем не критично.

У вас есть почти всё необходимое в файлах логов 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. Вот пример лога 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

Примечание: Здесь (выше, в качестве примера) мы видим IP-адреса клиентов, запрашивающих встроенный код Discourse с другого сервера.

Задача, стоящая перед нами....

Вернёмся к текущей задаче. «Секрет» заключается в том, чтобы отбросить спекуляции и догадки, и заняться увлекательным процессом: (1) фильтрацией и очисткой данных, (2) объединением данных и (3) анализом информации с ваших датчиков (файлов логов), чтобы сформировать (4) осведомлённость о ситуации (SA — Situational Awareness) о том, что происходит на вашем сайте.

Для старых LAMP-приложений у меня есть собственный код, написанный много лет назад, который записывает всю эту информацию в таблицу базы данных и выполняет анализ в реальном времени, подсчитывая «хиты» по IP-адресам (в качестве одного из примеров). Это позволяет мне быстро видеть, что, кто и откуда атакует сайт, поскольку для такой очистки, фильтрации и объединения данных требуется написание кода. (Это полезно, например, при DDoS-атаках и активности вредоносных ботов).

Для вас, @ossia, это не проблема, так как вы являетесь freeCodeCamp.org, а значит, у вас есть как знания для поиска отличных инструментов анализа логов (их множество в киберпространстве), так и возможность написать собственный код для быстрого и простого анализа в зависимости от сценария, который вы хотите изучить (ваша тема и проблема).

Я написал свой собственный код для старого устаревшего LAMP-приложения за несколько часов много лет назад, и я отнюдь не гений программирования, как бы ни называли меня многие в сфере кибербезопасности «легендой», LOL :slight_smile:

Подводя итог....

Ну, подводя итог…

У вас есть все данные, необходимые для формирования глубокой осведомлённости о том, «что происходит» на вашем сайте. Вы можете создать эту SA, очистив, отфильтровав, объединив и проанализировав данные ваших файлов логов. Существуют инструменты, которые могут помочь, но я всегда считаю проще написать собственный код, исходя из целей анализа (зависимый анализ). Ваше мнение может отличаться (YMMV), но вы легко сможете это сделать, так как вы являетесь freeCodeCamp.org и обладаете множеством технических навыков.

Я рекомендую вам отказаться от попыток получить SA с помощью Google Analytics и других сторонних приложений на базе JavaScript. Ничто не лучше собственных файлов веб-логов (и данных сессий из БД, если они у вас есть), и вам не нужно беспокоиться о том, «что может или не может быть заблокировано» и т.д. Файлы логов вашего веб-сервера содержат данные, необходимые для получения требуемой CSA (и их также можно при необходимости настроить).

В некоторых из моих кодов CSA я фактически перехватываю информацию о сессиях и данные логирования из HTTP-запросов, которые не логируются nginx, apache2 и другими веб-серверами (для получения дополнительной информации); однако я ещё не писал такого кода для Discourse, так как я не настолько «прост, как пирог» в разработке плагинов для Discourse (как гуру команды meta Discourse здесь), как это было с LAMP-приложениями. Я начал работать с Discourse всего несколько месяцев назад и пока не написал никакого собственного кода CSA для него (и, честно говоря, в этом году стараюсь писать меньше кода).

CSA основана на объединении данных с датчиков, и именно из CSA проистекает знание, необходимое для понимания того, какие действия нужно предпринять для устранения любой проблемы кибербезопасности.

Всего наилучшего в ваших поисках, и надеюсь, это поможет вам спать спокойнее :slight_smile:

Удачи!


Оригинальная (историческая) ссылка на CSA:

Оригинальная (историческая) ссылка на CSA:

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

(Ссылка только для тех, кто интересуется истоками и базовыми технологиями CSA)

Спасибо за вашу тяжелую работу, команда!

& огромное огромное спасибо
за упрощение
той двойной :nauseated_face: панели инструментов

Я просто закрою эту тему.

Discourse теперь размещает https://forum.freecodecamp.org/. Сайт работает молниеносно, скрипты спамеров больше не вызывают даже малейших проблем. Мы до сих пор не до конца понимаем, в чём заключалась проблема на Digital Ocean: возможно, это был «шумный сосед», возможно, машина была недостаточно мощной, а возможно, имели место какие-то технические неполадки — мы не уверены. Однако исходная проблема теперь полностью решена, и сообщество очень радо этому.