WordPress 4.7.1 REST API всё ещё раскрывает данные пользователей
Я обновил WordPress до версии 4.7.1
, и после этого попробовал получить список пользователей через REST API, что должно было быть исправлено, но мне всё равно удалось получить данные пользователей.
https://mywebsite.com/wp-json/wp/v2/users
Вывод:
[{"id":1,"name":"admin","url":"","description":"","link":"https:\/\/mywebsite\/author\/admin\/","slug":"admin","avatar_urls":{"24": ...
Список изменений из последней версии:
REST API раскрывал данные пользователей для всех, кто был автором публичного типа записи. WordPress 4.7.1 ограничивает это только теми типами записей, которые специально отмечены для отображения в REST API. Сообщено Krogsgard и Chris Jean.
После установки плагина Disable REST API
кажется, что всё работает нормально, но мне не нравится использовать плагин для каждой мелочи.
Вывод после использования плагина:
{"code":"rest_cannot_access","message":"Только аутентифицированные пользователи могут получить доступ к REST API.","data":{"status":401}}
Как можно исправить эту проблему без использования плагина, или почему даже после обновления это всё ещё существует?
РЕДАКТИРОВАНО 30.9.2017
Я обнаружил, что существует конфликт между плагинами Contact Form 7
и Disable REST API
, который приводит к ошибке 401 unauthorized
.
Когда вы пытаетесь отправить сообщение через форму Contact Form 7
, она делает запрос
wp-json/contact-form-7/v1/contact-forms/258/feedback
и отключение этого - не лучшая идея.

Этот фрагмент кода скроет конечные точки пользователей, записей и комментариев, возвращая ошибку 404, в то время как остальные вызовы API будут работать как обычно.
::ОБНОВЛЕНИЕ::
add_filter('rest_endpoints', function(){
$toRemove = ['users', 'posts', 'comments'];
foreach($toRemove as $val)
{
if (isset($endpoints['/wp/v2/'.$val])) {
unset($endpoints['/wp/v2/'.$val]);
}
if(isset($endpoints['/wp/v2/'.$val.'/(?P<id>[\d]+)'])) {
unset($endpoints['/wp/v2/'.$val.'/(?P<id>[\d]+)']);
}
}
return $endpoints;
});
::ОБНОВЛЕНИЕ::
Этот фрагмент кода удалит все стандартные конечные точки API.
<?php remove_action('rest_api_init', 'create_initial_rest_routes', 99); ?>

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

Куда вставлять этот пользовательский код? Вы не указали, где его нужно сохранить.

Это решение отключает все CRUD-операции с пользователями, смотрите эту реализацию только для GET-запросов: https://github.com/szepeviktor/wordpress-fail2ban/commit/98eb4f292a5a6bdae637f9410eb7bb9d6dffd81c

Как отключить вывод всех этих JSON-данных по адресу mydomain.com/wp-json/?

Это решение несовместимо с Gutenberg, который выдает:
Uncaught (in promise) Response { type: "basic", url: "/wp-json/wp/v2/users/?who=authors&per_page=100&_locale=user", redirected: false, status: 404, ok: false, statusText: "Not Found", headers: Headers, body: ReadableStream, bodyUsed: false }
Принудительная аутентификация на этих конечных точках была бы лучше.

/**
* Обернуть существующий стандартный callback, переданный в параметре, и создать
* новый permission callback с предварительными проверками,
* возвращающий стандартный callback в случае успеха.
*/
function permission_callback_hardener ($existing_callback) {
return function ($request) use($existing_callback) {
if (! current_user_can('list_users')) {
return new WP_Error(
'rest_user_cannot_view',
__( 'Извините, у вас нет прав для доступа к пользователям.' ),
[ 'status' => rest_authorization_required_code() ]
);
}
return $existing_callback($request);
};
}
function api_users_endpoint_force_auth($endpoints)
{
$users_get_route = &$endpoints['/wp/v2/users'][0];
$users_get_route['permission_callback'] = permission_callback_hardener($users_get_route['permission_callback']);
$user_get_route = &$endpoints['/wp/v2/users/(?P<id>[\d]+)'][0];
$user_get_route['permission_callback'] = permission_callback_hardener($user_get_route['permission_callback']);
return $endpoints;
}
add_filter('rest_endpoints', 'api_users_endpoint_force_auth');
- Эндпоинт(ы) не блокируется для администраторов (Gutenberg продолжает работать)
- Эндпоинт корректно отклоняет анонимных пользователей.
- Достаточно универсален для поддержки дополнительных эндпоинтов.
current_user_can
может быть дополнительно улучшен, сделан более универсальным.- Предполагается, что метод
GET
является первым для зарегистрированного маршрута (что пока всегда было верно)

Удалите ссылку на API из HTML head, если хотите.
// https://wordpress.stackexchange.com/a/211469/77054
// https://wordpress.stackexchange.com/a/212472
remove_action( 'wp_head', 'rest_output_link_wp_head', 10 );
Затем потребуйте аутентификацию для всех запросов.
// Вы можете потребовать аутентификацию для всех запросов REST API, добавив проверку is_user_logged_in к фильтру rest_authentication_errors.
add_filter( 'rest_authentication_errors', function( $result ) {
if ( ! empty( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_not_logged_in', 'Только аутентифицированные пользователи могут получить доступ к REST API.', array( 'status' => 401 ) );
}
return $result;
});
Это оставит вам желаемое сообщение.
Теперь, чтобы остановить перебор, вы можете использовать что-то вроде этого.
// https://perishablepress.com/stop-user-enumeration-wordpress/
// блокировка сканирования перебора пользователей в WP
// https://m0n.co/enum
if (!is_admin()) {
// стандартный формат URL
if (preg_match('/author=([0-9]*)/i', $_SERVER['QUERY_STRING'])) die();
add_filter('redirect_canonical', 'shapeSpace_check_enum', 10, 2);
}
function shapeSpace_check_enum($redirect, $request) {
// формат URL с постоянными ссылками
if (preg_match('/\?author=([0-9]*)(\/*)/i', $request)) die();
else return $redirect;
}
Ознакомьтесь с полной статьей для дополнительных методов.

Код .htaccess для блокировки всех сканирований авторов
# BEGIN block author scans
RewriteEngine On
RewriteBase /
RewriteCond %{QUERY_STRING} (author=\d+) [NC]
RewriteRule .* - [F]
# END block author scans
Вы можете удалить его с помощью функции, как предложено в принятом ответе

Я использовал этот небольшой код в файле function.php:
/**
* Доступ к REST API только для администраторов
*
* @return void
*/
function api_rest_only_for_admin_users() {
$current_user = wp_get_current_user();
if ( in_array('administrator', $current_user->roles ) ) {
return;
} else {
wp_die('Извините, у вас нет прав для доступа к этим данным','Доступ к REST API запрещен',403);
}
}
add_filter( 'rest_api_init', 'api_rest_only_for_admin_users', 99 );

Чтобы дополнить ответ BlueSuiter за 2017 год, вот решение для фильтрации пользователей по роли, которое делает его решение совместимым с редактором Gutenberg.
add_filter( 'rest_endpoints', function( $endpoints ) {
if(is_user_logged_in()) {
$user = wp_get_current_user();
$roles = array('editor', 'administrator', 'author');
if( array_intersect($roles, $user->roles ) ) return $endpoints;
}
if ( isset( $endpoints['/wp/v2/users'] ) ) unset( $endpoints['/wp/v2/users'] );
if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
return $endpoints;
});
Редактору Gutenberg необходимо делать запросы к REST API для получения информации об авторе. Поэтому необходимо пропустить как минимум все роли пользователей вашего сайта, которые имеют доступ к редактору Gutenberg, не забывая о пользовательских типах записей.
На публичной конечной точке REST API будет возвращаться ошибка 404, потому что is_user_logged_in всегда будет возвращать false.

Если вы используете брандмауэр для защиты вашего сайта на WordPress, то лучшим вариантом будет заблокировать через брандмауэр конечную точку API, которая раскрывает данные пользователей.
Определите URL https://example.com/wp-json/wp/v2/users
и просто заблокируйте его в брандмауэре.
Примечание: блокируйте только конечную точку, связанную с данными пользователей, а не весь REST API целиком.

// Блокировка пользовательских конечных точек WP Json API
function disable_custom_rest_endpoints( $endpoints ) {
$routes = array( '/wp/v2/users', '/wp/v2/users/(?P<id>[\d]+)' );
foreach ( $routes as $route ) {
if ( empty( $endpoints[ $route ] ) ) {
continue;
}
foreach ( $endpoints[ $route ] as $i => $handlers ) {
if ( is_array( $handlers ) && isset( $handlers['methods'] ) &&
'GET' === $handlers['methods'] ) {
unset( $endpoints[ $route ][ $i ] );
}
}
}
return $endpoints;
}
add_filter( 'rest_endpoints', 'disable_custom_rest_endpoints' );

Я создал полноценный плагин, который решает эту задачу и разместил его на GITHUB.
https://github.com/MRKWP/mrkwp-rest-permissions/
Я убедился, что использовал INIT для запуска действия, это гарантирует правильную работу подключаемых функций.
Также я использовал стандартную возможность WordPress 'Edit_posts' для API. Это обеспечивает корректную работу админки и редактора блоков.
Ознакомьтесь с этим плагином, чтобы решить любые проблемы, связанные с уязвимостью CVE-2017-5487, которые могут быть выявлены аудиторами безопасности.
Плагин также использует PHP CS, поэтому он будет работать с установкой VIP или в средах, где требуются соблюдение стандартов.
Надеюсь, это кому-то поможет. У меня ушло немало времени, чтобы заставить это хорошо работать на моих собственных сайтах и серверах.

Просматривая код, название "hardener" немного вводит в заблуждение, так как оно не укрепляет конечные точки, а заставляет их всегда отклонять запросы - вы просто устанавливаете его условно. Также вы вызываете функции активации и деактивации, которые не существуют. Но да, похоже, что это сработает.

Чтобы исправить эту проблему, сначала необходимо определить её источник.
- Используете ли вы SEO-плагины, такие как: All in One SEO Pack или Yoast? Попробуйте отключить их и проверьте снова.
- Используете ли вы плагин Jetpack? Попробуйте отключить его и проверьте снова.
Пожалуйста, сообщите мне, помогло ли это вам найти верное направление для решения.
Грубым способом решить эту проблему является простое блокирование URL в вашем файле .htaccess. https://mywebsite.com/wp-json/wp/v2/users
