WordPress — это удобная блог-платформа для публикации статей и управления ими, на которой базируется огромное число различных сайтов. Из-за своей распространенности эта CMS уже давно является лакомым куском для злоумышленников. К сожалению, базовые настройки не обеспечивают достаточного уровня защиты, оставляя многие дефолтные дырки незакрытыми. В этой статье мы пройдем типичным путем «типового» взлома сайта на Wordpress, а также покажем как устранить выявленные уязвимости.
Введение
На сегодняшний день WordPress среди систем управления контентом популярнее всего. Его доля составляет 60,4% от общего числа сайтов, использующих CMS-движки. Из них, согласно статистике, 67,3% сайтов базируется на последней версии данного программного обеспечения. Между тем за двенадцать лет существования веб-движка в нем было обнаружено 242 уязвимости различного рода (без учета уязвимостей, найденных в сторонних плагинах и темах). А статистика сторонних дополнений выглядит еще печальней. Так, компания Revisium провела анализ 2350 русифицированных шаблонов для WordPress, взятых из различных источников. В результате они выяснили, что более половины (54%) оказались зараженными веб-шеллами, бэкдорами, blackhat seo («спам») ссылками, а также содержали скрипты с критическими уязвимостями. Поэтому устраивайся поудобней, сейчас мы будем разбираться, как провести аудит сайта на WordPress и устранить найденные недостатки. Использовать будем версию 4.1 (русифицированную).
Индексирование сайта
Первым этапом любого теста обычно бывает сбор информации о цели. И тут очень часто помогает неправильная настройка индексирования сайта, которая позволяет неавторизованным пользователям просматривать содержимое отдельных разделов сайта и, например, получить информацию об установленных плагинах и темах, а также доступ к конфиденциальным данным или резервным копиям баз данных. Чтобы проверить, какие директории видны снаружи, проще всего воспользоваться Гуглом. Достаточно выполнить запрос Google Dorks типа site:example.com intitle:«index of» inurl:/wp-content/. В операторе inurl: можно указать следующие директории:
/wp-content/
/wp-content/languages/plugins
/wp-content/languages/themes
/wp-content/plugins/
/wp-content/themes/
/wp-content/uploads/
Если сможешь просмотреть /wp-content/plugins/, следующий шаг по сбору информации об установленных плагинах и их версиях значительно упрощается. Естественно, запретить индексирование можно с помощью файла robots.txt. Так как по умолчанию он не включен в установочный пакет WordPress, его необходимо создать самому и закинуть в корневую директорию сайта. Мануалов по созданию и работе с файлом robots.txt довольно много, поэтому оставлю эту тему для самоподготовки. Приведу лишь один из возможных вариантов:
User-Agent: *
Disallow: /cgi-bin
Disallow: /wp-login.php
Disallow: /wp-admin/
Disallow: /wp-includes/
Disallow: /wp-content/
Disallow: /wp-content/plugins/
Disallow: /wp-content/themes/
Disallow: /?author=*
Allow: /
Если в файлах, хранящихся в папке uploads, имеются сведения конфиденциального характера, добавляем к этому списку строчку: Disallow: /wp-content/uploads/. С другой стороны, в файле robots.txt не рекомендуется размещать ссылки на директории, которые были созданы специально для хранения чувствительной информации. Иначе этим самым ты облегчишь злоумышленнику задачу, так как это первое место, куда обычно все заглядывают в поисках «интересненького».
Определение версии WordPress
Еще один важный шаг — идентификация версии CMS. Иначе как подобрать подходящий сплоит? Существует три быстрых способа для определения используемой на сайте версии WordPress:
1. Найти в исходном коде страницы. Она указана в метатеге generator: /> или же в тегах : <link rel='stylesheet' id='twentyfifteen-style-css' href='http://.../wordpress/wp-content/themes/twentyfifteen/style.css?ver=4.1.1'… />.
2. Найти в файле readme.html (рис. 1), который входит в состав установочного пакета и находится в корне сайта. Файл может иметь и другие названия типа readme-ja.html.
3. Найти в файле ru_RU.po (рис. 2), который входит в состав установочного пакета и расположен по адресу /wp-content/languages/: «Project-Id-Version: WordPress 4.1.1\n».
Рис. 1. Версия WordPress в файле readme.html
Рис. 2. Подсматриваем версию WordPress в файле ru_RU.po
Один из вариантов защиты в данном случае — ограничить доступ к файлам readme.html и ru_RU.po с помощью .htaccess.
Автоматизация процесса тестирования
Исследованием безопасности WordPress занялись не вчера, поэтому существует достаточное количество инструментов, позволяющих автоматизировать рутинные задачи.
Nmap:
— определение версии и темы с помощью скрипта http-wordpress-info
nmap -sV --script http-wordpress-info <ip>
— подбор пароля по словарям
nmap -p80 --script http-wordpress-brute --script-args 'userdb=users.txt,passdb=passwords.txt' example.com
Metasploit:
— модуль для определения версии: auxiliary/scanner/http/wordpress_scanner;
— модуль для определения имени пользователя auxiliary/scanner/http/wordpress_login_enum.
WPScan:
— перечисление установленных плагинов: wpscan --url www.exmple.com --enumerate p;
— перечисление установленных тем: wpscan --url www.exmple.com --enumerate t;
— перечисление установленного timthumbs: wpscan --url www.example.com --enumerate tt;
— определение имени пользователя: wpscan --url www.example.com --enumerate u;
— подбор пароля по словарю для пользователя admin: wpscan --url www.example.com --wordlist wordlist.txt --username admin;
— подбор пароля с использованием связки имя пользователя / пароль с числом потоков, равным 50: wpscan --url www.example.com --wordlist wordlist.txt --threads 50.
Определение установленных компонентов
Теперь давай соберем информацию об установленных плагинах и темах независимо от того, активированы они или нет. Прежде всего такую информацию можно выудить из исходного кода HTML-страницы, например по JavaScript-ссылкам, из комментариев и ресурсов типа CSS, которые подгружаются на страницу. Это самый простой способ получения информации об установленных компонентах. Например, строчки ниже указывают на используемую тему twentyeleven:
<link rel="stylesheet" type="text/css" media="all" href="http://example.com/wp-content/themes/twentyeleven/style.css" />
<script src="http://example.com/wp-content/themes/twentyeleven/js/html5.js" type="text/javascript"></script>
Далее, HTTP-заголовки, такие как X-Powered-By, могут указывать на наличие плагина (например, на плагин W3 Total Cache).
Так как информация о плагинах не всегда отображается в исходном коде HTML-страницы, то обнаружить установленные компоненты можно с помощью утилиты WPScan (см. врезку). Только не забывай, что перебор путей плагинов зафиксируется в логах веб-сервера.
Получив данные об установленных компонентах, уже можно приступать к поиску уязвимостей своими силами либо найти общедоступные эксплойты на ресурсах типа rapid7 или exploit-db.
Определение имени пользователей
По умолчанию в WordPress каждому пользователю присваивается уникальный идентификатор, представленный в виде числа: example.com/?author=1. Перебирая числа, ты и определишь имена пользователей сайта. Учетная запись администратора admin, которая создается в процессе установки WordPress, идет под номером 1, поэтому в качестве защитной меры рекомендуется ее удалить.
Брутфорс wp-login
Рис. 3. Ошибки при аутентификации пользователя
Зная имя пользователя, можно попробовать подобрать пароль к панели администрирования. Форма авторизации WordPress на странице wp-login.php весьма информативна (рис. 3), особенно для злоумышленника: при вводе неправильных данных появляются подсказки о неверном имени пользователя или пароле для конкретного пользователя. Разработчикам известно о данной особенности, но ее решили оставить, так как подобные сообщения удобны для пользователей, которые могли забыть свой логин и/или пароль. Проблему подбора пароля можно решить, используя стойкий пароль, состоящий из двенадцати и более символов и включающий буквы верхнего и нижнего регистра, числа и спецсимволы. Или же, например, при помощи плагина Login LockDown.
Security-плагины для WordPress
— Login LockDown — ограничивает количество неудачных попыток авторизации;
— Revisium WordPress Theme Checker — ищет типичные вредоносные фрагменты в темах WordPress;
— Sucuri Security — проводит мониторинг и обнаружение вредоносного кода;
— iThemes Security (бывший Better WP Security) — многофункциональный плагин для защиты WordPress;
— BackUpWordPress — делает резервное копирование файлов и БД;
— Google Captcha (reCAPTCHA) — устанавливает капчу при регистрации, авторизации, восстановлении паролей и в форме комментариев.
Заливаем Shell
После того как мы сбрутили пароль, ничто не мешает залить шелл на скомпрометированный веб-ресурс. Для этих целей вполне сгодится фреймворк Weevely, который позволяет генерировать шелл в обфусцированном виде, что делает его обнаружение довольно сложным. Чтобы не вызывать подозрения, полученный код можно вставить в любой файл темы (например, в index.php) через редактор темы консоли WordPress. После чего с помощью того же Weevely можно подключиться к машине жертвы и вызывать различные команды:
python weevely.py http://test/index.php Pa$$w0rd
[+] weevely 3.1.0
[+] Target:test
[+] Session: _weevely/sessions/test/index_0.session
[+] Browse the filesystem or execute commands starts the connection
[+] to the target. Type :help for more information.
weevely> :help
Подключаем .htaccess
Для запрета доступа к чувствительной информации лучше воспользоваться файлом .htaccess — это файл конфигурации, используемый в Apache Web Server. Рассмотрим возможности этого файла с точки зрения безопасности. С его помощью можно: запретить доступ к директориям и файлам, заблокировать различные SQL-инъекции и вредоносные скрипты. Для этого стандартный файл .htaccess для CMS WordPress 4.1 нужно немного расширить. Чтобы закрыть список файлов и папок, добавляем:
Options +FollowSymLinks -Indexes
RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] заблокирует ссылки, содержащие кодировку Base64. Избавиться от ссылок, содержащих тег <script>:
RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
Противодействовать скриптам, пытающимся установить глобальные переменные или изменить переменную _REQUEST через URL:
RewriteCond %{QUERY_STRING} GLOBALS (=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST (=|\[|\%[0-9A-Z]{0,2})
Для противодействия SQL-инъекциям блокируем запросы к URL, содержащие определенные ключевые слова:
RewriteCond %{query_string} concat.*\( [NC,OR]
RewriteCond %{query_string} union.*select.*\( [NC,OR]
RewriteCond %{query_string} union.*all.*select [NC]
RewriteRule ^(.*)$ index.php [F,L]
Чтобы испортить жизнь распространенным хакерским утилитам, отфильтровываем определенные user-agent’ы:
SetEnvIf user-agent «Indy Library» stayout=1
SetEnvIf user-agent «libwww-perl» stayout=1
SetEnvIf user-agent «Wget» stayout=1
deny from env=stayout
Защищаем файлы
Неплохо было бы также ограничить и доступ к особо важным файлам, которые хранят конфигурацию или просто могут выдать злоумышленнику какую-то информацию. Можно выделить следующих кандидатов:
— wp-config.php, содержит имя БД, имя пользователя, пароль и префикс таблиц;
— .htaccess;
— readme.html и ru_RU.po, которые содержат версию WordPress;
— install.php.
Делается это следующим образом:
<Files имя_файла>
Order Allow,Deny
Deny from all
</Files>
Причем файл .htaccess, содержащий эти строки, должен находиться в той же директории, что и защищаемый файл. Затем запрещаем перечисление пользователей (помнишь, чуть выше мы говорили о том, как легко получить список пользователей?):
RewriteCond %{QUERY_STRING} author=\d
RewriteRule ^ /? [L,R=301]
Так, что еще? Можно разрешить вход только с указанных IP-адресов. Для этого создай файл .htaccess в папке wp-admin со следующими правилами:
AuthUserFile /dev/null
AuthGroupFile /dev/null
AuthName "Access Control"
AuthType Basic
order deny,allow
deny from all
allow from 178.178.178.178 # IP домашнего компа
allow from 248.248.248.248 # IP рабочего компа
Метод не слишком гибкий и применим только в случае, если ты работаешь с ограниченным числом фиксированных IP-адресов. В противном случае рекомендуется установить пароль на папку wp-admin через хостинг-панель (при наличии такого функционала).
WWW
Набор правил 5G Blacklist и 6G Blacklist beta от Perishable Press, который позволяет бороться с распространенными вредоносными URL-запросами для WordPress.
Дополнительные меры
К тому, что было сказано выше, можно добавить следующие рекомендации. Во-первых, использовать только актуальные версии WordPress и его компонентов — это позволит устранить известные уязвимости. Во-вторых, удалить неиспользуемые плагины и темы, которые также могут быть проэксплуатированы. В-третьих, скачивать темы и плагины WordPress из достоверных источников, например с сайтов разработчиков и официального сайта WordPress. Как и домашний ПК, нужно периодически проверять свой веб-ресурс веб-антивирусом, например AI-Bolit. Если у тебя есть доступ к веб-серверу, настрой права доступа к файлам и каталогам. Как правило, WordPress устанавливает все необходимые права на стадии установки, но в случае необходимости chmod можно выставить вручную. Для каталогов — chmod 755, для файлов — chmod 644. Убедись, что права 777 присвоены только тем объектам, которые в этом нуждаются (иногда это необходимо для нормальной работы некоторых плагинов). Если WordPress перестал нормально функционировать, поэкспериментируй с правами доступа: сначала попробуй 755, затем 766 и, наконец, 777. Для всех htaccess-файлов выставь chmod 444 (только чтение). Если сайт перестанет работать, попробуй поэкспериментировать со значениями 400, 440, 444, 600, 640, 644.
Перемести файл wp-config.php. Данный файл содержит информацию о настройках MySQL, префикс таблиц, секретные ключи и прочее. Поэтому его необходимо перенести для того, чтобы файл не был доступен из интернета. Если сайт не располагается в папке public_html, то перенеси файл wp-config.php в папку уровнем выше, и WordPress автоматически найдет его в этой корневой директории (применимо, если на хостинге имеется только один сайт на этой CMS).
Чтобы усложнить заливку шелла, отключи возможность редактирования темы через консоль WordPress. Для этого вставь следующую строчку в файл wp-config.php: define('DISALLOW_FILE_EDIT', true);.
Еще одно слабое место — файл install.php (что в папке wp-admin). Поэтому его лучше удалить, заблокировать или изменить. Выполни один из вариантов:
- Просто удали этот файл — после установки в нем нет больше необходимости.
- Запрети доступ к файлу с помощью .htaccess.
- Переименуй оригинальный файл install.php (например, install.php.old) и создай новый файл install.php со следующим содержимым:
<?php header("HTTP/1.1 503 Service Temporarily Unavailable"); ?>
<?php header("Status 503 Service Temporarily Unavailable"); ?>
<?php header("Retry-After 3600"); // 60 minutes ?>
<?php mail("your@email.com", "Database Error", "There is a problem with teh database!"); ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Error Establishing Database Connection</title>
</head>
<body>
<h1>Error Establishing Database Connection</h1>
<p>We are currently experiencing database issues. Please check back shortly. Thank you.</p>
</body>
</html>
Помимо уведомления посетителей сайта, данный скрипт выполняет следующие действия:
— отправляет клиенту и поисковым системам код состояния 503 («Сервис временно недоступен»);
— указывает промежуток времени, через который клиенты и поисковые системы могут вернуться на сайт (настраиваемый параметр);
— уведомляет по электронной почте о проблеме с БД для принятия соответствующих мер.
Дело в том, что в ранних версиях WordPress (<= 2.7.1) при сбоях MySQL (например, при DDoS-атаке) CMS дает возможность переустановиться. Кроме того, это может произойти и при сбое/повреждении одной из таблиц WordPress. В частности, атака возможна, когда повреждена таблица wp_options (в WordPress 2.6.2) или wp_users (в WordPress 2.0.3 и 2.0.11). То есть в разных версиях WP разные таблицы главные при проверке в инсталляторе — это может быть либо таблица wp_options, либо wp_users.
Ну и наконец, отключи регистрацию новых пользователей, если в этом нет необходимости (рис. 4). Если все же на сайте предусматривается регистрации, позаботься о том, чтобы новые пользователи после прохождения регистрации получали минимальные привилегии.
Рис. 4. Отключение регистрации новых пользователей
Полезные ссылки
Hardening WordPress codex.wordpress.org/Hardening_WordPress
Десять шагов для защиты твоего WordPress-блога habrahabr.ru/post/62814
Каждый второй русифицированный шаблон для WordPress заражен или уязвим www.securitylab.ru/analytics/456835.php
Презентация по взлому сайтов WordPress xakep.ru/2015/04/01/wordpress-hack
Заключение
WordPress — довольно крупный и сложный продукт, со своими плюсами и минусами. К сожалению, в дефолтной конфигурации его безопасность находится под большим вопросом, так как даже обычный скрипткидис при наличии прямых рук и интернетов сможет пробить защиту. Поэтому настоятельно рекомендую проверить свой ресурс таким же образом, как мы делали это в статье. И если обнаружишь недостатки — пофиксить их, чтобы свести шансы злоумышленника на компрометацию ресурса к минимуму.
Впервые опубликовано в журнале Хакер #196.
Автор: Сергей Сторчак, PentestIT
Подпишись на «Хакер»
Комментарии (11)
cmepthuk
10.06.2015 08:38+1Apache, конечно, хорошо, но как мне кажется — практичнее закрывать всё же со стороны фронтенда (в виде nginx). Об этом накидал небольшую заметку (не вставил её текст прямо сюда по причине обновлений время от времени).
cmepthuk
10.06.2015 08:50+1И по поводу
robots.txt
— приведу как альтернативу следующий пример (где и когда он был найден — история умалчивает):
Disallow: /*/wp-* Disallow: /*/feed/* Disallow: /*/*?s=* Disallow: /*/*.js$ Disallow: /*/*.inc$ Disallow: /*/trackback/* Disallow: /*/xmlrpc.php User-agent: ia_archiver Disallow: /
nikitasius
10.06.2015 10:32+1Собственно, если у вас php5-fpm+nginx, то как минимум:
- закрываем несчастный xmlrpc.php
- вешаем на wp-admin auth от nginx'а или делаем блог «только для себя»(чтобы не было других писателей)
- отключает autoindex для папок
- вводим фильтрацию args снова в nginx
- делаем фильтр для wp-login.php только для ввода пароля, чтобы смотреть защищенные статьи.
По пунктам:1)
location ~* ^/(\.htaccess|xmlrpc\.php)$ { return 404; }
2)
location ~* ^/wp-admin/(.*(?<!(\.php)))$ { auth_basic "protected by password"; auth_basic_user_file users/somefile; #еще параметры }
и/или во втором случае
location ~* (/wp-admin/|/wp-config\.php|/wp-config-sample\.php|/wp-mail\.php|/wp-settings\.php|/wp-signup\.php|/wp-trackback\.php|/wp-activate\.php|/wp-links-opml\.php|/wp-load\.php|/wp-comments-post\.php|/wp-blog-header\.php|/wp-login\.php|/wp-includes/.*?\.php|/wp-content/.*?\.php) { auth_basic "protected by password"; auth_basic_user_file users/somefile; #еще параметры }
3)autoindex off;
4)
if ($args ~* "(attachment_id|media_category|attachment_category|select|eval|duplicate|base64|substring|preg_replace|create_function)") { #действия; }
5) символическая ссылка на wp-login.php. К примеру, как «wp-postpass.php».
location ~* (/wp-postpass\.php) { if ($args ~ "^action=postpass$") { set $wppostpass 1; } if ($wppostpass ~ 0) { return 403; } #еще параметры }
В итоге люди смогут ввести пароль к публикации, да и только.
Я когда себе ставил, опубликовал на хабре свои шаги.emostar
12.06.2015 22:54Против подбора пароля ботами в админку существует великолепный плагин Brute Force Login Protection, который банит IP через .htaccess после заданного количества неуспешных попыток входа.
dslava
16.06.2015 17:57И есть еще одна хорошая альтернатива. У популярного плагина Jetpack есть опция блокировать попытки несанкционированного входа (модуль называется Protect). Для этого используется единая база данных и нагрузка при попытке брут-форса плавно переходит на сервера Jetpack-а. В том же модуле можно и жестко прописать нужные IP-адреса в список разрешенных адресов. Активировал его несколько месяцев назад и на моем не слишком посещаемом проекте было за это время детектировано около 7.000 попыток несанкционированного входа.
dslava
16.06.2015 18:05После предложенного
RewriteCond %{QUERY_STRING} GLOBALS (=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST (=|\[|\%[0-9A-Z]{0,2})
сервер начал выдавать 500ю ошибку.
skomoroh
А есть где-нить полная база с описанием всех возможных уязвимостей по движкам?
А если еще и с версиями движков и лекарством — то совсем хорошо.
lostpassword
И чтобы
ломалапроверяла автоматически. «Введите URL и нажмите ОК».))Если на exploit-db еще не заходили — можете попробовать посмотреть там.
skomoroh
Софтинки способные ломать сайт по урлу врят-ли в паблике есть, иначе школота уже давно бы все сайты на cms-ках положили. А вот знать где соломки себе подстелить, даже чисто теоретически, без активных действий по защите, было-бы неплохо.