Почему следует использовать mod_security?

Перед тем как я начал работать над mod_security, для контроля своего веб-трафика я использовал Short. Все прекрасно работало. Я задал в Short слова, которые меня интересовали, и он уведомлял меня всякий раз, когда одно из них появлялось во входящем потоке. Но я хотел большего. Я хотел свободы в определении сложных правил, а также возможности выполнения различных действий, определенных в HTTP. Использование же IDS отнимает очень много времени, а сама система является слишком дорогостоящей.

В то время я также пытался комбинировать использование mod_rewrite и mod_setenvif. Используя mod_rewrite, очень легко обнаружить слова drop и table, а затем перенаправить клиента с такого URL, тем самым предотвратив атаку. Это, конечно, убережет Вас от атак начинающих хакеров, но более опытный злоумышленник может просто использовать тот же URL, как и приведенный выше, но вместо метода GET использовать метод POST. Так как переменные метода POST обычно не обрабатываются в большинстве модулей, то данная атака успешно пройдет.
Убедившись в необходимости создания нового инструмента, я встал перед выбором: взять Java и создать полноценный реверс-прокси или создать модуль Apache, используя огромный объем существующего кода. Первый вариант требовал большего времени на работу, а результат, вероятно, был бы полезен только для очень ограниченного круга людей. Я же хотел создать что-то более гибкое и легкое в использовании, поэтому я выбрал второй вариант.
Возвращаясь к нашему примеру URL, для предотвращения SQL инъекции “drop table” с помощью mod_security, необходимо добавить в конфигурацию Apache следующее:
SecFilter “drop[[:space:]]table”
Единственным параметром директивы является регулярное выражение, на соответствие с которым будут проверяться все входящие запросы. Также этого можно достигнуть, используя mod_rewrite, но отличие в том, что mod_security обнаруживает и предотвращает атаки проводимые, как с использованием GET, так и с использованием POST.
Добавление возможности контроля метода POST запросов было большой проблемой для Apache 1.3.x, так как он не поддерживал фильтры.
Установка и конфигурирование
Наилучшим вариантом установки mod_security является компиляция его из исходных текстов. А если Вы используете Windows, и у Вас нет компилятора, то посетите сайт ModSecurity и скачайте оттуда уже скомпилированный модуль:
$/path/to/apache/bin/apxs -cia mod_security.c
# /path/to/apache/bin/apachectl stop
# /path/to/apache/bin/apachectl start
Перед тем как начать создавать правила, добавьте несколько строк в файл конфигурации httpd.conf:

# Включает или выключает движок фильтра
 SecFilterEngine On
 # Проверка правильности кодирования URL
 SecFilterCheckURLEncoding On
 # Проверка UNICODE кодирования
 SecFilterCheckUnicodeEncoding Off
 # Использовать только байты из этого диапазона
 SecFilterForceByteRange 0 255
 # Вести лог только для подозрительных запросов
 SecAuditEngine RelevantOnly
 # Имя файла лога
 SecAuditLog logs/audit_log
 # Вывод отладочной информации (установлен минимальный уровень)
 SecFilterDebugLog logs/modsec_debug_log SecFilterDebugLevel 0
 # Осуществлять проверку POST запросов
 SecFilterScanPOST On
 # Для подозрительных запросов по умолчанию писать в лог
 # и возвращать HTTP ответ с кодом 500
 SecFilterDefaultAction “deny,log,status:500″

Я оставил комментарии в тексте, поэтому назначение директив вполне очевидно. Данная конфигурация активирует mod_security, но больше ничего не делает. Желательно всегда начинать с простой конфигурации и добавлять в нее директивы по необходимости.
Так что он может делать?
Даже с такой простой конфигурацией mod_security делает два очень полезных дела. Во-первых, он выполняет несколько техник, предназначенных для предотвращения скрытых угроз, а во-вторых, он унифицирует весь входящий трафик. Это очень поможет, когда мы начнем добавлять правила фильтрации в конфигурацию сервера. Представьте, что Вы хотите запретить пользователям выполнять на сервере бинарный файл ps, используя регулярное выражение /bin/ps ax. Если не использовать mod_security, то это регулярное выражение будет отлавливать только идентичные обращения, а /bin//ps ax или /bin/ps%20ax, или /bin/./ps ax будет пропускать. А вот список того, что выполнит mod_security:

Удаляет множественные слеши (//).
Удаление директорий, ссылающихся на самих себя (./).
Обрабатывать и “\”, и “/” (только для Windows).
Производить декодирование URL.
Заменять пустые байты (%00) пробелами.

Также выполняется ряд проверок:

Проверка URL кодирования.
Проверка UNICODE кодирования.
Проверка частей запросов на определенные значения.

Действия
Всякий раз, когда срабатывает правило, выполняется некоторая последовательность действий. В большинстве случаев выполняются действия по умолчанию (они задаются директивой SecDefaultAction), но также возможно настроить действия на каждое правило с помощью второго аргумента в директиве SecFilter или третьего аргумента в SecFilterSelective. Вот список возможных действий:

deny – отвергает запрос.
allow – останавливает проверку правил и допускает запрос.
status:nnn – возвращает HTTP ответ с кодом nnn.
redirect:url – направляет запрос на абсолютный URL url.
exec: cmd – выполняет скрипт cmd.
log – запись запроса в лог ошибок.
nolog – не вносить запрос в лог.
pass – пропустить текущее правило и перейти к следующему
pause:nnn – приостановить обработку запроса на nnn миллисекунд. Будьте очень осторожны с этим действием. Apache остается занятым при остановке обработки. Вы фактически можете помочь злоумышленникам провести DoS-атаку.

Следующие действия влияют на порядок выполнения правил, подобно тому, как работает mod_rewrite:

chain – следующее правило в цепочке. Если правило в цепочке не срабатывает, то следующие за ним правила пропускаются.
skipnext:n – пропустить следующие n правил.

Правила фильтрации
Правила бывают двух видов. В простейшей форме:
SecFilter keyword
Проверяет наличие keyword (регулярное выражение) в первой строке входящего запроса (которая выглядит как GET /index.php HTTP/1.1) или в теле POST, если оно есть. Вместо него Вы можете использовать:
SecFilterSelective “произвольный список keyword, разделенный |”
Эта директива позволяет лучше контролировать то, что Вы собираетесь фильтровать (и тратит на выполнение меньше циклов процессора). Вместо продолжения дальнейшего разбора синтаксиса я приведу несколько интересных примеров. Пускай они дадут Вам стимул к действиям. Наиболее полезные правила всегда создаются при решении реально существующих проблем.

Данное правило пропустит все запросы от одного IP адреса (с моей рабочей станции). Другие правила обрабатываться не будут. Так как данные запросы не несут злого умысла, то они не попадают в лог:
SecFilterSelective REMOTE_ADDR “^IP_ADDRESS_HERE$” nolog,allow

Данное правило позволяет мне получить полный доступ с моего ноутбука, когда я в дороге. Так как я не знаю, какой IP адрес у меня будет, то доступ гарантируется всем клиентам, содержащим строку “Blend 42″ в поле User-Agent. Это слабая защита, но как метод идентификации – достаточно интересная.
SecFilterSelective HTTP_USER_AGENT “Blend 42″

Данное правило предотвращает SQL инъекции в cookie. Если существует cookie, запрос выполняется только в том случае, если cookie состоит только из цифр.
SecFilterSelective COOKIE_sessionid “!^(|[0-9]{1,9})$”

Данное правило требует наличия заголовков HTTP_USER_AGENT и HTTP_HOST в каждом запросе. Злоумышленники часто работают, используя простые средства (в т.ч. telnet), и не посылают всех заголовков, которые отсылает браузер. Такие запросы будут отвергнуты, помещены в лог и проконтролированы.
SecFilterSelective “HTTP_USER_AGENT|HTTP_HOST” “^$”

Данное правило запрещает закачку файлов на сервер. Это простая, но эффективная защита; отказ запросов основан на типе контента, используемого при закачке файлов.
SecFilterSelective “HTTP_CONTENT_TYPE” multipart/form-data

Это правило заносит в лог запросы без заголовка “Accept” для последующей его проверки. “Ручные” запросы часто не содержат всех HTTP заголовков. Заголовок “Keep-alive” следующий кандидат на такую проверку.
SecFilterSelective “HTTP_ACCEPT” “^$” log,pass

Это правило отсылает мне письмо всякий раз, когда начальник вновь забывает свой пароль 🙂 . Тут у нас две директивы. Первая срабатывает только тогда, когда запрошен определенный файл (в данном случае файл, отображающий сообщение о неверном пароле). Вторая директива проверяет имя пользователя на соответствие с “ceo”. И если имя совпадает, то вызывается внешний скрипт:
SecFilterSelective REQUEST_URI “login_failed\.php” chain
SecFilterSelective ARG_username “^ceo$” log,exec:/home/apache/bin/notagain.pl

Данное правило отправляет поискового робота Googlebot обратно на сайт Google. Используется заголовок User-Agent. Данный запрос не попадает в лог.
SecFilter HTTP_USER_AGENT “Google” nolog,redirect:http://www.google.com

Данное правило проверяет все переменные JavaScript, пропуская переменную с именем html. Другие же переменные JavaScript отвергаются, так как они могут вызвать ошибки в некоторых приложениях (актуально для CMS средств).
SecFilter “ARGS|!ARG_html” “<[:space:]*script”

И в завершение покажем пример, как использовать множественную настройку mod_security. Это означает, что можно создавать правила для определенного пути. Запомните директиву SecFilterInheritance. C ее помощью мы сообщаем mod_security, что необходимо игнорировать все правила вышестоящего контекста.
SecFilterForceByteRange 32 126
# Использование этой директивы НЕ игнорирует правила вышестоящего контекста
SecFilterInheritance Off
# Разработчики часто используют специальные переменные
# для работы в Debug режиме. Эти два правила
# разрешают использовать переменную “debug”, но только для внутренней сети
SecFilterSelective REMOTE_ADDR “!^192.168.254.” chain
SecFilterSelective ARG_debug “!^$”

Производительность
У меня никогда не было проблем с производительностью при использовании mod_security. Тем не менее, на практике производительность немного падает. На реальных веб-сайтах один запрос страницы может вызвать множество запросов на графику, таблицы стилей и JavaScript файлы. Mod_security не рассматривает дочерние запросы, только если это не задать явно следующей директивой:
SecFilter DynamicOnly
Операции ввода/вывода всегда были узким местом. Убедитесь, что на работающем сервере выключен режим отладки и используйте максимальный уровень ведения лога только тогда, когда он Вам действительно нужен. В представленной выше конфигурации лог ведется только для “подозрительных” запросов, т.е. для тех, которые приводят к срабатыванию фильтров.
Другие возможности
Внутренний chroot
Если Вам когда-нибудь приходилось использовать chroot для веб-сервера, то Вы, вероятно, знаете какая это непростая задача. Для mod_security эта сложность исчезает. Достаточно использовать одну директиву:
SecChrootPath /chroot/home/web/apache
Единственное требование – это, чтобы путь сервера был подобен обычному пути (например: /home/web/apache).

Подмена сигнатуры сервера
Злоумышленники и автоматические скрипты для сбора данных о сервере часто используют HTTP-заголовок “Server”, который отсылается с каждым запросом. Вы можете изменить его путем внесения изменения в код Apache, но также это можно сделать, используя следующую директиву. Вы можете воспользоваться этой возможностью только для Apache 1.x. Модуль mod_headers, включенный в Apache 2.x, перехватывает исходящие заголовки и меняет их “на лету”:
SecServerSignature “Microsoft-IIS/5.0″
Ссылки

Автор: Иван Ристик (Ivan Ristic)
Перевод: Сипягин Максим
Оригинал документа (en)

Дополнительная информация к статье:

Пример использования mod_security:
SecFilterEngine on – включаем модуль
SecFilterCheckURLEncoding On – проверять кодировку URL
SecFiIterForceByteRange 32 126 – диапазон допустимых символов
SecAuditLog var/mod_security/log – логи модуля
SecFilterDefaultAction “deny,log,status:406″ – действие по умолчанию (deny)
SecFilter /bin/cp – для запрета обращения к командам
SecFilter ” \. \. /” – для запрета перехода в родительскую директорию
SecFilter “delete [[: space:]] +from” – обнаруживаем SQL injection
SecFilter “<(.|\п)+>” И SecFilter “<[[:space:]]*script” – в защите от XSS-атак нам поможет этот фильтр.
SecFilter ууу log,exec:/home/user/www/detected.php – при обнаружении атаки можно запустить