
Открытый wp-json — это адрес, по которому свежеустановленный WordPress добровольно отдаёт любому желающему список авторов сайта вместе с их учётными именами. Никакого взлома для этого не нужно: достаточно дописать восемь символов к адресу сайта и нажать Enter. Половина работы злоумышленника при подборе пароля состоит в том, чтобы узнать логин, и эту половину система делает за него сама.
Что WordPress отдаёт по адресу /wp-json/
Начиная с версии 4.7, вышедшей в конце 2016 года, в ядре работает программный интерфейс REST API. Он нужен для дела: через него работает блочный редактор, мобильное приложение, часть админки и множество плагинов, которые обмениваются данными с сайтом без перезагрузки страницы. Точка входа — адрес /wp-json/, а конкретные наборы данных лежат по адресам вида /wp-json/wp/v2/posts, /wp-json/wp/v2/pages, /wp-json/wp/v2/users.
С записями и страницами всё логично: они и так опубликованы, отдавать их не страшно. Проблема в последнем адресе. По умолчанию /wp-json/wp/v2/users доступен без всякой авторизации и возвращает набор данных по каждому пользователю, у которого есть хотя бы одна опубликованная запись. В ответе три интересных поля:
id— числовой идентификатор. Идентификатор 1 почти всегда принадлежит первой созданной учётной записи, то есть администратору.name— отображаемое имя, то, что вы видите под статьями.slug— сокращённое имя пользователя. Именно оно интересно посторонним: при обычной установке WordPress формирует его из логина, и в подавляющем большинстве сайтовslugи логин совпадают буква в букву.
То есть сайт публично сообщает: администратор существует, зовут его так-то, а входит он под таким-то именем. Остаётся подобрать пароль.
Отдельно стоит сказать, что это не ошибка и не уязвимость в привычном смысле. Разработчики WordPress считают такое поведение штатным: авторы публичных материалов и так подписаны на страницах, значит, их имена не секрет. Логика спорная ровно в одном месте — она не учитывает, что учётное имя для входа и подпись под статьёй у большинства сайтов совпадают.
Проверка за минуту: три адреса
Проверять чужой сайт не нужно — проверьте свой. Откройте браузер и введите по очереди три адреса, подставив своё доменное имя.
Первый: /wp-json/wp/v2/users. Если в ответ пришёл текст в фигурных скобках со словами id, name и slug — список пользователей открыт. Если пришло сообщение об ошибке с кодом 401 — доступ закрыт.
Второй: /wp-json/wp/v2/users/1. Это запрос конкретно первой учётной записи. Иногда общий список закрыт плагином, а обращение по идентификатору продолжает работать.
Третий: /?author=1. Здесь никакого REST API нет вовсе, это старый механизм архивов автора. WordPress получает запрос с идентификатором и перенаправляет на адрес архива вида /author/имя-пользователя/. Учётное имя оказывается прямо в адресной строке.
Третий адрес важнее всего, потому что про него забывают. Владелец ставит плагин безопасности, тот закрывает REST API, проверка по первому адресу показывает ошибку — и на этом успокаиваются. А перебор по ?author= с единицы до двадцати выдаёт всех авторов сайта за пятнадцать секунд.
Ещё один канал, который закрывают реже всего: адрес /wp-json/oembed/1.0/embed?url= с подставленным адресом любой вашей статьи. В ответе есть поле с именем автора. Он остаётся рабочим, даже когда основной список пользователей уже закрыт.
Чем опасен известный логин
Само по себе знание логина сайт не ломает. Опасность в том, что оно превращает бессмысленный перебор в осмысленный.
Подбор пары «логин плюс пароль» вслепую практически безнадёжен: комбинаций слишком много. Подбор пароля к известному логину — совершенно другая задача. Скрипты идут по спискам утёкших паролей, по типовым сочетаниям с названием домена, по датам. Если пароль администратора выглядит как название сайта с годом на конце, его подберут за считаные часы, а на большинстве сайтов никто этого даже не заметит: в журнале не хранятся неудачные попытки входа, а нагрузку от перебора списывают на «хостинг тормозит».
Второй канал — интерфейс xmlrpc.php. Он старше REST API и умеет объединять несколько команд в один запрос, что позволяет за одно обращение проверить десятки паролей. Если сайт не используется мобильным приложением и не публикует записи из внешних систем, этот файл нужно закрывать.
Третье, о чём редко думают: нагрузка. Даже неуспешный перебор — это тысячи обращений к wp-login.php, каждое из которых поднимает PHP и делает запрос к базе. На недорогом тарифе сайт от этого ощутимо замедляется, а иногда падает по превышению лимита процессов. Как выглядит поток автоматических обращений в логах и чем он отличается от живых посетителей, я разбирал на живом примере в материале 3000 ботов в день с чужих серверов: как выглядит атака на поведенческие изнутри логов.
И последнее по порядку, но не по значению: успешный вход в админку — это не «испорченная главная страница». Это чужие страницы в индексе, редиректы посетителей на посторонние сайты и, чаще всего, потеря позиций, которую замечают через несколько недель. Что происходит с сайтом после такого захода и как выглядит уборка, описано в статье Как защитить сайт от взлома.
Как закрыть список пользователей
Способов несколько, и они решают разные части задачи. Разумно комбинировать, а не выбирать один.
| Мера | Что закрывает | Что может сломать |
|---|---|---|
| Убрать маршруты пользователей из REST API | Список и карточки авторов в /wp-json/ | Плагины, выводящие авторов через API; редактор не страдает |
| Требовать авторизацию для всего REST API | Весь интерфейс целиком для гостей | Формы, поиск, магазин, календари записи — многое перестаёт работать |
| Отдавать 404 на запросы вида /?author= | Перебор по идентификаторам | Архивы авторов, если они реально нужны сайту |
| Сделать учётное имя отличным от логина | Смысл утечки: имя больше не равно логину | Ничего, но меняется адрес архива автора |
| Не использовать логин admin и производные | Первую строчку любого словаря подбора | Ничего |
| Ограничить число попыток входа | Перебор пароля независимо от логина | Можно случайно заблокировать себя |
| Закрыть xmlrpc.php | Массовый перебор пакетами | Мобильное приложение, публикацию из внешних систем |
| Двухфакторное подтверждение входа | Вход даже при известном пароле | Ничего, кроме привычки |
Самая недооценённая строка здесь — четвёртая. Пока учётное имя совпадает с логином, вы закрываете каналы утечки по одному и всё равно рискуете пропустить какой-нибудь редкий. Если же сокращённое имя пользователя отличается от логина, то любая утечка теряет ценность: посторонний узнает подпись под статьями, а не имя для входа. Меняется оно в профиле пользователя или запросом к базе в поле user_nicename; после смены старый адрес архива автора перестанет открываться, поэтому для сайтов, где эти архивы проиндексированы, нужен редирект со старого адреса на новый.
Про архивы авторов вообще. На сайте с одним автором они бессмысленны: содержимое архива дословно повторяет ленту блога, и это готовый дубль. Их разумно закрывать метатегом noindex или отключать вовсе — заодно исчезает и канал с ?author=. Логика решения, что из служебных страниц закрывать, а что оставлять, разобрана в материале Стоит ли запрещать индексацию страниц категорий и архивов.
Ограничение частоты обращений, которое не мешает работе
Закрыть выдачу имён — половина дела. Вторая половина — сделать так, чтобы перебор паролей упирался в стену.
Лимит попыток входа. Базовая мера: после нескольких неудачных попыток адрес блокируется на время. Настройки по умолчанию у большинства решений разумны — пять попыток, блокировка на двадцать минут, при повторе на сутки. Важная деталь: блокировка должна работать не только для wp-login.php, но и для xmlrpc.php, иначе перебор просто уходит в обход.
Ограничение частоты на уровне сервера. Более надёжный вариант, потому что запрос отсекается до запуска PHP и не создаёт нагрузки. В nginx это делается директивами ограничения частоты для конкретных адресов, в Apache — модулями ограничения. Смысл в том, чтобы разрешить, скажем, десять обращений в минуту к странице входа и отбрасывать всё сверх этого. Живой человек в такой лимит не упирается никогда. Как настраиваются подобные ограничения на уровне веб-сервера, показано в материале Внутрихостовая директива: оптимизация и безопасность веб-сервера.
Дополнительный пароль на страницу входа. Базовая авторизация на уровне сервера для wp-login.php отсекает практически весь автоматический перебор: скрипт получает код 401 и уходит. Неудобство в том, что паролей становится два.
Чего делать не стоит — переименовывать страницу входа. Мера популярная, но защита слабая: адрес всё равно определяется, а вот проблем от неё много. На сайтах, которые я веду, вход остаётся по стандартному адресу, потому что нестандартный регулярно приводит к потере доступа после обновления плагина, и потому что защиту дают ограничение частоты и второй фактор, а не спрятанная дверь.
Чего делать нельзя: отключать REST API целиком
Самый популярный совет из статей — вставить код, который закрывает REST API для всех неавторизованных. Он действительно закрывает утечку, но вместе с ней и часть работы сайта.
Что ломается: формы обратной связи на современных конструкторах, поиск и фильтры, работающие без перезагрузки, корзина и оформление заказа в магазине, календари записи, блоки подгрузки материалов, интеграции с внешними системами. Ломается не сразу и не заметно: сайт открывается, кнопка нажимается, а заявка не уходит. Владелец обнаруживает это через две недели по отсутствию звонков.
Правильный подход — точечный: убрать конкретные маршруты, отдающие пользователей, оставив остальное работать. Это одна короткая функция в файле функций дочерней темы или в отдельном небольшом плагине. И обязательно с проверкой: если пользователь авторизован и у него есть право просматривать список пользователей, маршруты должны работать, иначе перестанет открываться раздел «Пользователи» в админке.
Второе, чего делать не надо, — закрывать /wp-json/ в robots.txt. Этот файл адресован поисковым роботам и никого ни от чего не защищает. Скрипт перебора его не читает. Более того, перечислив в robots.txt служебные адреса, вы получите публичный список того, что считаете чувствительным.
Общий принцип: закрывать нужно узко и проверять результат. Тяжёлые плагины-комбайны, включающие сотню галочек разом, обычно и создают проблемы — про подход без них я писал в статье Как защитить WordPress без плагинов-комбайнов: настройки, которые не тормозят сайт.
Чеклист: десять минут на проверку
Пройдите по списку и зафиксируйте результат. Каждая строка проверяется прямо в браузере, без доступа к серверу.
| Проверка | Что открыть | Нормальный результат |
|---|---|---|
| Список пользователей в API | /wp-json/wp/v2/users | Ошибка доступа, а не список имён |
| Карточка первого пользователя | /wp-json/wp/v2/users/1 | Ошибка доступа |
| Перебор по идентификатору | /?author=1 и /?author=2 | 404, а не переход на архив автора |
| Данные автора через oEmbed | /wp-json/oembed/1.0/embed?url=адрес статьи | Ответ без учётного имени |
| Логин администратора | Профиль пользователя в админке | Не admin, не имя домена, не имя владельца |
| Совпадение логина и подписи | Поле «Имя пользователя» и адрес архива автора | Различаются |
| Интерфейс xmlrpc | /xmlrpc.php | 403 или 404, если приложение не используется |
| Ограничение попыток входа | Настройки защиты или сервера | Лимит включён и для страницы входа, и для xmlrpc |
| Пароль администратора | Менеджер паролей | Длинный, случайный, нигде больше не используется |
| Работа сайта после правок | Форма заявки, поиск, корзина, редактор записи | Всё работает как раньше |
Последняя строка обязательна. Любая правка в этой области требует контрольной проверки: отправьте тестовую заявку, откройте редактор записи и сохраните черновик, добавьте товар в корзину. Если что-то из этого сломалось — вы закрыли лишнее.
Частые вопросы
Если у меня блог с одним автором и никаких плагинов, это всё вообще актуально? Более чем. Чем проще сайт, тем чаще на нём один администратор, он же автор, с логином вида названия компании и паролем, придуманным при установке. Ровно этот случай перебирается первым.
Плагин безопасности пишет, что REST API защищён. Этого хватает? Проверьте вручную все четыре адреса из чеклиста. Практика показывает, что многие решения закрывают общий список пользователей, но оставляют открытым обращение по идентификатору или адрес oEmbed. Верить нужно результату проверки, а не надписи в панели.
Правда ли, что через wp-json можно украсть содержимое сайта? Через него можно выгрузить то, что и так опубликовано: тексты записей, заголовки, категории. Черновики, приватные записи и персональные данные без авторизации не отдаются. Копирование текстов — реальная, но отдельная проблема, и решается она не закрытием API: у любого, кто хочет забрать ваши статьи, есть браузер.
Влияет ли закрытие REST API на индексацию? Напрямую нет: поисковые роботы читают обычные страницы, а не программный интерфейс. Косвенно влияет очень даже — если из-за неаккуратной блокировки перестанут работать фильтры каталога или подгрузка товаров, робот увидит пустые разделы.
Я сменил user_nicename, и архив автора перестал открываться. Это нормально? Да, адрес архива строится именно из этого поля. Если старый адрес был в индексе и на него есть ссылки, настройте перенаправление со старого адреса на новый. Если архивы авторов вам не нужны — просто закройте их от индексации, и вопрос снимется.
Стоит ли вообще удалять учётную запись администратора и заводить новую? Удалять не нужно, достаточно двух вещей: логин, не совпадающий ни с чем очевидным, и отдельная учётная запись с правами автора для публикации статей. Тогда даже полностью открытый список пользователей покажет постороннему имя автора, у которого нет прав на установку плагинов.
Коротко
- По умолчанию WordPress отдаёт список авторов по адресу
/wp-json/wp/v2/usersбез авторизации, а полеslugу большинства сайтов совпадает с логином. - Второй канал утечки — перебор адресов вида
/?author=1, третий — запрос oEmbed. Закрывать нужно все три. - Известный логин превращает перебор пароля из безнадёжной задачи в решаемую, а сам перебор дополнительно нагружает сервер.
- Полное отключение REST API для гостей ломает формы, поиск, корзину и календари. Убирать нужно только маршруты пользователей.
- Самая надёжная мера — сделать учётное имя отличным от логина: тогда утечка перестаёт быть утечкой.
- Robots.txt здесь не защита, а публикация списка того, что вы прячете.
Проверка занимает десять минут и не требует ни доступа к серверу, ни специальных программ — откройте четыре адреса и посмотрите на ответы. Если вместо ошибки увидели имена, поправьте это сегодня, а заодно посмотрите, что ещё сайт отдаёт лишнего: техническая доработка сайта обычно начинается именно с таких мелочей. Разобрать техническую и поисковую часть вместе, на своём примере, можно на SEO-консультации по вашему сайту — со мной напрямую, а не через менеджера. Если же нужен не разовый разбор, а системная работа, посмотрите, как устроено SEO-продвижение бизнеса в поиске.
Увеличьте позиции и продажи вашего сайта
Профессиональное SEO-продвижение с гарантией результата. Выберите подходящую услугу:
Остались вопросы по продвижению?
Меня зовут Анатолий Кузнецов, я SEO-оптимизатор с 20-летним стажем. Разберу ваш сайт, отвечу на вопросы и подскажу, что улучшить для роста позиций в Яндексе и Google.
Связаться со мной →
Комментарии
Лаврентий Елагин
Проверил свой сайт — список пользователей отдаётся, там три имени, включая моё. Плагин безопасности стоит, галочка «защитить REST API» включена. Как так?
Анатолий Кузнецов автор
Обычная история: у многих плагинов эта галочка блокирует API только для запросов без реферера или отсекает конкретные типы обращений, а не маршрут пользователей. Проверьте по очереди все три адреса из чеклиста — часто оказывается, что общий список закрыт, а обращение к /wp-json/wp/v2/users/1 работает. Дальше я бы не стал искать в плагине нужную галочку, а сделал две вещи руками. Первая — убрать маршруты пользователей фильтром, это несколько строк в дочерней теме. Вторая, и более важная, — привести логины к виду, отличному от подписи под статьями: тогда даже открытый список перестанет что-либо давать. После правок обязательно откройте раздел «Пользователи» в админке и сохраните черновик записи, чтобы убедиться, что для авторизованных всё работает.
Нонна Курбская
Вставила код полного отключения REST API из первой попавшейся статьи. Через неделю выяснилось, что не работает форма заявки. Хорошо, что не месяц.
Анатолий Кузнецов автор
Именно поэтому я отдельным разделом написал, чего делать нельзя. Такие вставки живут в интернете годами и до сих пор кочуют из статьи в статью, хотя с появлением блочного редактора и современных форм последствия стали заметно тяжелее. Уберите этот код и замените точечным отключением маршрутов пользователей. И заведите себе привычку: после любой правки, связанной с безопасностью, прогоняйте короткий список — отправить тестовую заявку, открыть и сохранить черновик, проверить поиск по сайту, а для магазина ещё и добавление товара в корзину. Три минуты проверки экономят недели без заявок. Отдельно посмотрите в почте, не было ли писем от формы за эту неделю: если сообщения не уходили, часть обращений вы, к сожалению, потеряли.
Фаина Извольская
Логин у меня admin, менять страшно — вдруг сайт сломается. Реально ли это опасно, если пароль длинный?
Анатолий Кузнецов автор
Опасно не тем, что пароль подберут, а тем, что ваш сайт будут перебирать постоянно, создавая паразитную нагрузку. Логин admin стоит первой строкой в любом словаре, и такие сайты попадают в автоматические списки для регулярных попыток. Менять сам логин через админку WordPress не даёт, но задача решается безопасно: создайте вторую учётную запись с правами администратора и другим именем, зайдите под ней, убедитесь, что всё работает, и удалите старую, указав при удалении передачу всех её записей новому пользователю. Записи и авторство при этом не потеряются. Если удалять страшно, оставьте старую запись, но понизьте её права до подписчика и смените пароль — эффект почти тот же.
Розалия Смолятина
Не поняла про oEmbed. Это тоже нужно закрывать или можно не трогать?
Анатолий Кузнецов автор
Этот адрес нужен для того, чтобы ваши статьи красиво разворачивались превью, когда кто-то вставляет ссылку на них у себя. В ответе среди прочего есть отображаемое имя автора. Само по себе это не страшно — подпись и так видна под статьёй. Опасным оно становится, когда отображаемое имя совпадает с логином, а такое бывает у сайтов, где профиль никогда не заполняли. Поэтому решение то же самое: разведите логин и то, что показывается публично. Тогда oEmbed можно спокойно оставить работать, он полезен. Если развести имена по каким-то причинам нельзя, ответ этого адреса фильтруется отдельно, но я бы начал с более простого пути.
Севериан Аргунов
Проверил /?author=1 — переходит на архив с моим логином прямо в адресе. А я был уверен, что всё закрыто.
Нестор Чекалин
Согласен насчёт переименования страницы входа. У меня после обновления плагина адрес слетел, а старый уже не работал. Восстанавливал доступ через базу.
Пантелей Щелоков
В логах у меня по несколько тысяч обращений к xmlrpc.php в сутки. Файлом не пользуюсь, приложение не ставил. Отключил — нагрузка на сервер сразу упала.
Анатолий Кузнецов автор
Так и должно быть, и это самый быстрый способ снять паразитную нагрузку на типовом сайте. Уточню только, что именно проверить после отключения. Во-первых, если у вас настроены уведомления о входящих ссылках с других сайтов, они перестанут приходить — механизм работает через тот же файл. Во-вторых, некоторые плагины резервного копирования и мониторинга обращаются к сайту именно так, поэтому загляните в них и убедитесь, что задачи по расписанию продолжают отрабатывать. И закрывайте файл на уровне сервера, а не плагином: тогда запрос не поднимает PHP вообще, и эффект по нагрузке заметно больше. Плагин отдаёт ответ уже после запуска движка, то есть ресурсы всё равно тратятся.
Фаддей Небогатов
Полезная таблица. Особенно строка про то, что ломается при каждом варианте — обычно про это в статьях молчат.
Олимпиада Шаховская
А архивы авторов у меня закрыты в robots.txt. Получается, канал с ?author= всё равно открыт?
Капитолина Мещерякова
Сделала отдельную учётную запись автора для публикации статей, администратора оставила только для работ. Простое решение, а не додумалась сама.
Ярослава Наумовская
Вопрос про двухфакторное подтверждение: не мешает ли оно, если заходить в админку по несколько раз в день?
Мелания Шелестова
У нас интернет-магазин, и после чужих экспериментов с REST API перестала работать корзина. Проверять после правок — золотое правило.