Discourse + Web Application Firewall (WAF) mod_security

Какой WAF рекомендуется использовать перед Discourse?

Недавно мне удалось модифицировать конфигурацию nginx, установленную в контейнере Docker с Discourse, чтобы включить ModSecurity и набор правил CRS (Core Rule Set) от OWASP (Open Web Application Security Project).

На данный момент мои тесты показывают отличные результаты, и ModSecurity, похоже, отлично работает с Discourse из коробки.

Какой опыт использования WAF и Discourse у других пользователей? Есть ли у вас какие-либо рекомендации, кроме ModSecurity?

Заметка о важности WAF: они обеспечивают широкую защиту от уязвимостей 0-day не только в веб-приложении, но и во всем стеке его зависимостей.

Мне указали, что у команды Discourse есть программа вознаграждения за обнаружение уязвимостей, что отлично!

…однако, разумеется, не существует на 100% защищённого кода. Ошибки случаются, и даже не обязательно, чтобы ошибка была допущена вашей командой — критическая уязвимость может появиться в вашем проекте.

Например, вспомним недавнее событие с CVE-2019-11043: ошибка в php-fpm, которую можно было использовать для выполнения произвольного кода на серверах с уязвимыми версиями php-fpm и nginx.

Однако CVE-2019-11043 можно полностью нейтрализовать, блокируя запросы, содержащие символы возврата каретки или перевода строки:

Это лишь пример того, как установку веб-приложения можно скомпрометировать через критическую уязвимость, даже если в самом коде этого приложения нет никаких недостатков. В случае с Discourse при установке через Docker подтягивается огромное количество внешнего ПО, что может сделать Discourse уязвимым в будущем.

В приведённом выше случае ModSecurity CRS уже блокирует запросы с символами перевода строки и возврата каретки, эффективно защищая в противном случае уязвимые веб-серверы от CVE-2019-11043 до появления уязвимости (0-day).

Подобные уязвимости обнаруживаются постоянно. Использование WAF, такого как ModSecurity, для веб-приложений даёт значительные преимущества.

Это плохая идея, и её не рекомендуется использовать. Преимущество такого подхода для JavaScript-приложения крайне ограничено, а сложность настройки хостинга значительно возрастает.

по теме: я только что нашёл это руководство от @joelradon о том, как установить Discourse с NAXSI WAF в nginx перед контейнером Docker с Discourse:

Это не более поддерживаемо, чем то, что вы спрашиваете выше.

Если это поможет, я могу также добавить там тег «не поддерживается»?

Я считаю, что стремление к некоему волшебному устройству, которое автоматически устраняет проблемы, в настройке Discourse в какой-то степени ошибочно. У нас есть программа вознаграждений, и мы исправляем уязвимости в Discourse в течение нескольких часов после их сообщения. Сайты по умолчанию запускают tests-passed, который в сегодняшнем случае включает коммиты от сегодняшнего дня.

Конечно, если вы используете программное обеспечение, которое было взломано несколько лет назад, и у вас нет возможности обновить его из-за… причин… то WAF имеет смысл, так как он может вас спасти. Но в случае с Discourse я считаю это в лучшем случае ошибочным.

Мне удалось успешно сохранить изменения конфигурации nginx ModSecurity при повторных запусках команды launcher rebuild app следующим образом:

Сначала обновляем локальную копию скрипта install-nginx, который был получен из репозитория discourse_docker и клонирован в /var/discourse/.

cd /var/discourse/image/base
cp install-nginx install-nginx.`date "+%Y%m%d_%H%M%S"`.orig

# Добавляем блок для проверки модуля modsecurity nginx непосредственно перед загрузкой исходного кода nginx
grep 'ModSecurity' install-nginx || sed -i 's%\(curl.*nginx\.org/download.*\)%# mod_security\napt-get install -y libmodsecurity-dev modsecurity-crs\ncd /tmp\ngit clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git\n\n\1%' install-nginx

# Обновляем строку configure, чтобы включить модуль ModSecurity, проверенный выше
sed -i '/ModSecurity/! s%^[^#]*./configure \(.*nginx.*\)%#./configure \1\n./configure \1 --add-module=/tmp/ModSecurity-nginx%' install-nginx

# Добавляем строку в секцию очистки
grep 'rm -fr /tmp/ModSecurity-nginx' install-nginx || sed -i 's%\(rm -fr.*/tmp/nginx.*\)%rm -fr /tmp/ModSecurity-nginx\n\1%' install-nginx

Обратите внимание, что Dockerfile, отвечающий за выполнение скрипта install-nginx, выполняется при сборке образа. А образ собирается только командой Discourse перед загрузкой в Docker Hub. Когда выполняется команда Discourse ./launcher rebuild app, она запускает (если доступны обновления) docker pull, который загружает последний образ Docker Discourse из Docker Hub. Опять же, это не пересобирает образ, не выполняет Dockerfile и не выполняет изменённый выше скрипт install-nginx.

Единственный способ (о котором я знаю), чтобы запустить обновлённый bash-скрипт install-nginx (который выполняется Dockerfile), — заставить Docker собрать новый образ. Например, это заставляет Docker собрать новый образ с именем discourse_modsecurity, который будет собран с использованием локально изменённого скрипта install-nginx:

docker build --tag 'discourse_modsecurity' /var/discourse/image/base/

К сожалению, я не смог найти способ указать launcher использовать пользовательский образ (указание run-image использует указанный образ напрямую, без выполнения шаблонов против него — как необходимо для фактической конфигурации [а не просто установки] nginx). Поэтому мы заменяем переменную image, определённую в скрипте launcher, чтобы использовать наш новый локальный образ Docker с именем discourse_modsecurity.

# Заменяем строку "image="discourse/base:<version>" на 'image="discourse_modsecurity"'
grep 'discourse_modsecurity' launcher || sed --in-place=.`date "+%Y%m%d_%H%M%S"` '/base_image/! s%^\(\s*\)image=\(.*\)$%#\1image=\2\n\1image="discourse_modsecurity"%' /var/discourse/launcher

Теперь добавляем новый файл шаблона для настройки конфигураций nginx, чтобы включить необходимые файлы/блоки modsecurity.

cat << EOF > /var/discourse/templates/web.modsecurity.template.yml
run:
  - exec:
     cmd:
       - sudo apt-get install -y modsecurity-crs
       - cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
       - sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
       - sed -i 's^\(\s*\)[^#]*SecRequestBodyInMemoryLimit\(.*\)^\1#SecRequestBodyInMemoryLimit\2^' /etc/modsecurity/modsecurity.conf
       - sed -i '/nginx/! s%^\(\s*\)[^#]*SecAuditLog \(.*\)%#\1SecAuditLog \2\n\1SecAuditLog /var/log/nginx/modsec_audit.log%' /etc/modsecurity/modsecurity.conf

  - file:
     path: /etc/nginx/conf.d/modsecurity.include
     contents: |
        ################################################################################
        # File:    modsecurity.include
        # Version: 0.1
        # Purpose: Defines mod_security rules for the discourse vhost
        #          This should be included in the server{} blocks nginx vhosts.
        # Author:  Michael Altfield <michael@opensourceecology.org>
        # Created: 2019-11-12
        # Updated: 2019-11-12
        ################################################################################
        Include "/etc/modsecurity/modsecurity.conf"
        
        # OWASP Core Rule Set, installed from the 'modsecurity-crs' package in debian
        Include /etc/modsecurity/crs/crs-setup.conf
        Include /usr/share/modsecurity-crs/rules/*.conf

  - replace:
     filename: "/etc/nginx/conf.d/discourse.conf"
     from: /server.+{/
     to: |
       server {
         modsecurity on;
         modsecurity_rules_file /etc/nginx/conf.d/modsecurity.include;

EOF

И добавляем этот шаблон (templates/web.modsecurity.template.yml) в блок templates yaml-файла конфигурации нашего приложения, чтобы он выглядел примерно так:

[root@osestaging1 discourse]# vim /var/discourse/containers/app.yml
...
[root@osestaging1 discourse]# grep -A 6 'templates:' /var/discourse/containers/app.yml
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ratelimited.template.yml"
  - "templates/web.socketed.template.yml"
  - "templates/web.modsecurity.template.yml"
[root@osestaging1 discourse]# 

Теперь при пересборке приложения Discourse в Docker оно будет использовать ваш новый образ Docker discourse_modsecurity с nginx и modsecurity, а также настроит nginx для использования OWASP CRS.

/var/discourse/launcher rebuild app

Я согласен с вами, что ModSecurity или аналогичные WAF не являются панацеей.

Но я знаю (по крайней мере?) одну уязвимость в RoR, которая затронула Discourse, которая могла бы быть смягчена механизмом, подобным ModSecurity.

Когда мы исправляли эту уязвимость, мы увидели в наших логах, что она использовалась в дикой природе как минимум на одном из наших форумов до того, как CVE стала общедоступной и было выпущено исправление. Это не привело к утечке информации, но это произошло только потому, что мы делали некоторые вещи иначе, чем при стандартной установке (то есть, повезло).

Однако я не уверен, что возросшая сложность перевешивает дополнительную безопасность.

Я сравниваю WAF с антивирусами на основе сигнатур, которые, на мой взгляд, не очень полезны в средах с ограниченной поверхностью атак (например, на ваших серверах под управлением не-Windows).

Они не смогут перехватить всё, но теоретически смогут обнаруживать распространённые паттерны атак (например, SQL-инъекции) и известные уязвимости (например, упомянутую выше уязвимость в RoR) в различных программных продуктах.

Я вижу смысл в их использовании, особенно в корпоративной среде, где запущено множество приложений, работающих на неизвестных технологиях, и необходимо обеспечить защиту каждого приложения от таких эксплойтов без необходимости анализировать каждое из них по отдельности. Это позволяет превратить задачу размера N×M в задачу размера N+M.

Стоит ли запускать их на вашем сайте — вопрос отдельный, и вам предстоит провести собственный анализ рисков (возможных сбоев) и преимуществ (уровня защиты).