в Wordpress

В прошлом посте мы рассматривали методы повышения безопасности WordPress с помощью nginx. В данной же записи мы рассмотрим дополнительные меры противодействия сбору информации о работающей CMS (и плагинах), версии WP и некоторым типам атак на сайт.

Помогать нам будет уже не только nginx — но и fail2ban вкупе с некоторыми дополнениями и настройками самого WP.

Обозначим проблемы безопасности, которые «по умолчанию» имеют место быть:

  1. WordPress вставляет данные о своей версии в различные места генерируемого контента;
  2. Стандартные настройки его имеют довольно много потенциально слабых мест;
  3. Стандартные настройки доступа к сайту позволяют без особых трудностей собрать потенциальному взломщику довольно много информации;
  4. «Из коробки» отсутствуют какие-либо средства против перебора директорий и паролей.

Ниже мы примем требуемые меры к их решению. Данная запись будет со временем обновляться и дополняться.

Скрываем версию

Для решения данной задачи можно воспользоваться данным плагином. Для его установки переходим в директорию с плагинами WordPress и скачиваем его:

$ cd /var/www/sitename.org/docs/wp-content/plugins/
$ wget -O remove-wp-version.php https://goo.gl/J3Ttq8

После чего переходим в панель управления плагинами и активируем его.
Плагин удаляет теги <meta generator="..." /> и подстроку ?ver=%версия% из запросов к css и js файлам. Никаких настроек не имеет.

Настраиваем WP

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

В нем попунктно рассматриваются потенциально слабые места и описаны средства их устранения.

Настраиваем nginx

Считаем что в качестве фронтенда у нас работает nginx, и несколько пересмотрев его конфиг из прошлой записи решил довести его до вида, который описан ниже.

Запрещаем вывод листинга файлов, если nginx собран с модулем ngx_http_autoindex_module:

# Раскоментировать если nginx собран с модулем "ngx_http_autoindex_module"
autoindex off;

Закрываем доступ к ридмишкам, некоторым типам логов и системных файлов:

# Запрещаем доступ ко всем ULR-ам, которые ЗАКАНЧИВАЮТСЯ следующими вхождениями
location ~* /((wp-config|plugin_upload|xmlrpc).php|(readme|license|changelog).(html|txt|md)|(debug|access|error)(.|_)log)$ {
  return 444;
}

Стоит обратить внимание, что мы закрываем доступ к xmlrpc.php (часто используемого для DDoS сайта), после чего приложения-клиенты для WP наверняка перестанут работать

Запрещаем запросы, которые содержат следующие вхождения:

# Запрещаем доступ ко всем URL, которые СОДЕРЖАТ следующие вхождения
location ~* /.*((wp-config|xmlrpc).*(php(_bak|~|#)|txt|old|bak|save|orig(|inal)|swp|swo)).*$ {
  return 444;
}

Запрещаем запросы, в параметрах которых есть следующие подстроки:

# Запрещаем все URL-ы, в параметрах (..?a=evil) которых есть следующие вхождения
if ($query_string ~* "^(.*)(wp-config.php|dl-skin.php|xmlrpc.php|uploadify.php|admin-ajax.php|local.xml)(.*)$") {
  return 444;
}
# Для противодействия SQL-инъекциям <http://habrahabr.ru/company/xakep/blog/259843/>
if ($query_string ~* "(concat.*\(|union.*select.*\(|union.*all.*select)") {
  return 444;
}

Запрещаем вывод информации об авторах (и получение списка логинов через перебор ID) через запросы вида ?author=%ID%:

# Запрещаем вывод информации об авторах
if ($query_string ~* "author=[0-9]") {return 301 $scheme://$host/;}

Запрещаем запуск скриптов из директорий загрузок и files:

# Запрет для загруженных скриптов
location ~* /(?:uploads|files)/.*\.(php|cgi|py|pl)$ {return 444;}

Запрещаем ссылки вида //blog.ru/wp/wp-content/.. и //blog.ru/page/wp-content/.., которые часто встречаются в инструментах перебора:

# Запрещаем ссылки вида //blog.ru/wp/wp-content/.. и //blog.ru/page/wp-content/..
location ~* /(wp|page)/.*wp-.*/.*$ {return 444;}

Для аудита сайтов на WordPress очень распространен такой инструмент как «WPScan» и на противодействии ему остановимся чуть подробнее. Даже после того, как мы приняли необходимые меры по скрытию версии WP — данный инструмент безошибочно определяет её путем получения некоторых публичных файлов и сравнения их контрольных сумм.
Крайняя версия WPScan (на момент написания данной записи 2.8 от 2015-06-22) ориентирована на два файла — /wp-includes/css/buttons-rtl.css и /wp-includes/js/tinymce/wp-tinymce.js.gz. Он формирует с виду очень «правильный» запрос, с рефералом и необходимыми плюшками, всё как полагается. Если просто закрыть доступ к этим файлам то пострадает функционал WP, так как они необходимы в ряде случаев для корректной работы панели администратора. Как же решить эту задачку?
WPScan отправляет реферал, всегда равный %scheme%://%sitename%/. Но файлы то нам нужны только для панели администрирования, и соответственно в при их «легальном» запросе в реферале должна присутствовать подстрока /wp-admin. Если её нет — значит запрос можно детектировать как пришедший «извне». Этой то особенностью мы и воспользуемся :)

Более того, для понижения информативности вывода данного инструмента мы запретим ему читать файл robots.txt. Как запретить доступ для WPScan? Дело всё в том же реферале. Поисковые роботы не отправляют никакого реферала для его чтения, а WPScan шлёт всё тот же %scheme%://%sitename%/ :)

# Активно препятствуем fingerprinting-у утилиты WPScan (http://wpscan.org/)
location = /wp-includes/css/buttons-rtl.css {
  if ($http_referer !~* "/wp-admin") {return 404;}
}
location = /wp-includes/js/tinymce/wp-tinymce.js.gz {
  if ($http_referer !~* "/wp-admin") {return 404;}
}
# И так же для WPScan (который запрашивая robots.txt посылает referer сайта, хотя его быть не
# должно) закрываем доступ к этому самому robots.txt, делая вывод тулзы ещё менее информативный
location = /robots.txt {if ($http_referer != "") {return 404;}}

Хардкорно имитируем ошибки 404 при запросе директорий, характерных для WP:

# Закрываем доступ к корню следующих директорий
location = /wp-content/ {return 404;}
location = /wp-includes/ {return 404;}
location = /wp-content/plugins/ {return 404;}
location = /wp-content/uploads/ {return 404;}
location = /wp-content/themes/ {return 404;}
location = /wp-content/languages/ {return 404;}
location = /wp-content/languages/plugins/ {return 404;}
location = /wp-content/languages/themes/ {return 404;}

И ещё один интересный момент — запрещаем прямой доступ к директориям плагинов. Делается это в первую очередь для предотвращения их раскрытия путем простого перебора имен директорий:

# Закрываем прямой доступ у содержимому корневых директорий плагинов (для предотвращения
# их ракрытия)
location ~* /wp-content/plugins/([0-9a-z\-_]+)(/|$) {return 404;}

Закрываем доступ к файлам переводов (которые зачастую так же содержат версию используемой CMS):

# Закрываем доступ к файлам перевода (для невозможности раскрыть версию WP)
location ~ /wp-content/languages/(.+)\.(po|mo)$ {return 404;}

Запрещаем доступ следующим User-Agent-ам:

# http://habrahabr.ru/post/168739/
if ($http_user_agent ~* (nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan|nic.ru|monitoring|semalt|virusdie|indy|perl|php|python|wpscan)) {return 403;}

И блокируем всех, у кого User-Agent пустой (или почти пустой):

# Блокируем конкретные юзер-агенты, в частности - пустой и "-"
if ($http_user_agent ~ ^(|-|_)$) {return 403;}

Все описанные выше настройки я бы рекомендовал описать в одном файле (secure_wordpress.inc), и подключать в конфигурации каждой секции server { ... } с помощью include secure_wordpress.inc; где установлен WordPress.

Настраиваем fail2ban

Для установки fail2ban на CentOS 7 необходимо подключить репозиторий EPEL:

$ yum install epel-release

Поставить сам fail2ban и поставить его в автозапуск:

$ yum install fail2ban
$ fail2ban-server -V
Fail2Ban v0.9.2

Copyright (c) 2004-2008 Cyril Jaquier, 2008- Fail2Ban Contributors
Copyright of modifications held by their respective authors.
Licensed under the GNU General Public License v2 (GPL).

Written by Cyril Jaquier <[email protected]>.
Many contributions by Yaroslav O. Halchenko <[email protected]>.
$ chkconfig fail2ban on

После чего открыть файл /etc/fail2ban/fail.local (если его нет, то скопировать fail.conf) и добавить в него секции, описанные ниже. Давай сперва чуть подробнее разберем их предназначение и настройки.

Первая (под именем nginx-404) занимается тем, что мониторит запросы к нашему серверу которые завершились кодами 403, 404 или 444 (т.е. файл не был найден или в доступе к нему nginx отказал) и отправляет пользователя в бан на 90 секунд, если в течение 10 секунд таких ответов было 25 штук. Данная мера необходима для того, чтоб предотвратить (довольно сильно усложнить) перебор наличия файлов и директорий на сервере.

ВНИМАНИЕ!

С указанными параметрами надо быть очень осторожным! Так как если, например, пользователь перейдет на страницу, на которой будут размещены изображения размещающиеся на самом сайте, но по какой-либо причине они не будут доступны (перемещены, удалены) в установленном количестве — пользователь будет забанен. Или же если будет какой-либо JS скрипт который, например, в цикле будет запрашивать несуществующий ресурс на сайте, пользователь так же будет забанен. Укажите те настройки, которые будут безопасны для «легальных» посетителей в первую очередь!

Вторая отвечает за ограничение попыток авторизации (защита от брутфорса). Если кто либо в течение 20 секунд будет пользователь произведет 10 попыток войти (отправит POST запрос к /wp-login.php) — он будет оправлен в бан на 1200 секунд.

Как дополнительная мера — поставить плагин Google Captcha (reCAPTCHA), который добавит на страницу авторизации ещё и капчу.

[nginx-404]
enabled  = true
port     = http,https
filter   = nginx-404
action   = iptables-multiport[name="nginx_404", port="http,https"]
maxretry = 25
findtime = 10
bantime  = 90
logpath  = /var/www/*/log/*access*.log
           /var/www/*/log/*error*.log

[wp-auth]
enabled  = true
port     = http,https
filter   = wp-auth
action   = iptables-multiport[name="wp_auth", port="http,https"]
maxretry = 10
findtime = 20
bantime  = 1200
logpath  = /var/www/*/log/*access*.log
           /var/www/*/log/*error*.log

Не забудь поправить пути к логам твоего сервера

А так же создать файлы /etc/fail2ban/filter.d/nginx-404.conf:

[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) .* HTTP/\d+\.\d+" (403|404|444) .*$
ignoreregex =

И /etc/fail2ban/filter.d/wp-auth.conf:

[Definition]
failregex = ^<HOST> .* "POST .*wp-login.php HTTP/\d+\.\d+".*$
ignoreregex =

После чего перезапустить fail2ban и убедиться, что при его запуске не возникли никакие ошибки:

$ service fail2ban restart
$ cat /var/log/fail2ban.log | grep ERROR

Полевые тестирования

Теперь давай попробуем проверить, насколько наши меры эффективны. Будем использовать онлайн-сканер WordPress Security Scan и WPScan собственной персоной.

Результаты «WordPress Security Scan» до и после (картинки кликабельны):

Вывод WPScan до (под символами X скрыты актуальные значения, соответственно):

$ wpscan -r --url somedomain.com

[+] URL: http://somedomain.com/
[+] Started: Sun Jul 1 01:01:01 2015

[+] robots.txt available under: 'http://somedomain.com/robots.txt'
[+] Interesting entry from robots.txt: http://somedomain.com/*/wp-*
[+] Interesting entry from robots.txt: http://somedomain.com/*/feed/*
[+] Interesting entry from robots.txt: /*/*?s=*
[+] Interesting entry from robots.txt: http://somedomain.com/*/*.js$
[+] Interesting entry from robots.txt: http://somedomain.com/*/*.inc$
[+] Interesting entry from robots.txt: http://somedomain.com/*/trackback/*
[+] Interesting entry from robots.txt: http://somedomain.com/*/xmlrpc.php
[+] Interesting header: SERVER: XXXXXXXXX

[+] WordPress version 4.X.X identified from advanced fingerprinting

[+] WordPress theme in use: null

[+] Name: null
 |  Location: http://somedomain.com/wp-content/themes/null/
 |  Style URL: http://somedomain.com/wp-content/themes/null/style.css
 |  Theme Name: /dev/null
 |  Description: 

[+] Enumerating plugins from passive detection ...
 | 3 plugins found:

[+] Name: XXXXXXXXXXXXX
 |  Location: http://somedomain.com/wp-content/plugins/XXXXXXXXXXXXX/

[+] Name: XXXXXXXXXXXXXX - vX.XX
 |  Location: http://somedomain.com/wp-content/plugins/XXXXXXXXXXXXXX/
 |  Readme: http://somedomain.com/wp-content/plugins/XXXXXXXXXXXXXX/readme.txt

[+] Name: XXXXXXXXXXXXXXX
 |  Location: http://somedomain.com/wp-content/plugins/XXXXXXXXXXXXXXX/

[+] Finished: Sun Jul 1 01:01:43 2015
[+] Requests Done: 112
[+] Memory used: 6.102 MB
[+] Elapsed time: 00:00:42

И результат её же работы, но после:

$ wpscan -r --url somedomain.com

[+] URL: http://somedomain.com/
[+] Started: Sun Jul 1 01:01:01 2015

[+] Interesting header: SERVER: XXXXXXXXX

[i] WordPress version can not be detected

[+] WordPress theme in use: null

[+] Name: null
 |  Location: http://somedomain.com/wp-content/themes/null/

[!] The target seems to be down

Профит? Отож :) Для того чтоб скрыть баннер фронтэнд сервера необходимо собрать nginx из исходников, процесс сборки был ранее описан в этой записи.

Вместо заключения

Не существует такой системы, которую невозможно взломать, и об этом стоит помнить. Никто не отменял XSS уязвимости которые находятся с завидной периодичностью, а также стоит помнить что существуют критичные уязвимости, которые публично нигде не освещены. Для раскрытия той же версии WP методом fingerprint-а существует так же большое количество методов в целом.

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

Бабахнуть комментарий

Комментарии

  1. Подскажите пожалуйста где лучше разместить файл secure_wordpress.inc ?
    Т.к. у меня проблема произошла, на одном посте при попытке нажатия на редактирование поста (в админку перенаправляет сразу) получаю ошибку 403. Надеюсь Ваша инструкция спасет.

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

    • Думаю, перво-наперво есть смысл остановить почтового демона (прикрыть 25 порт). После — искать ошибки отправки писем почтового демона, выяснив — откуда «ноги растут». После — прикрыть найденную лазейку. Есть вероятность, что злоумышленник получил доступ к исполнению произвольного кода на вашей системе.
      Так же есть смысл посмотреть что скажет, например, тот же Manul — не панацея, но как одно из средств диагностики использовать можно