WordPress REST API — zabezpieczenie i optymalizacja
Opublikowano: 9 kwietnia 2026 · Kategoria: WordPress / Bezpieczeństwo
WordPress REST API (dostępne pod /wp-json/) to potężne narzędzie dla headless
WordPress, aplikacji mobilnych i integracji z zewnętrznymi serwisami. Niestety to też jeden
z najpopularniejszych wektorów ataków — ujawnia listę użytkowników, umożliwia brute-force i
jest używane przez boty do skanowania podatności. Oto jak go zabezpieczyć bez psucia
funkcjonalności.
Czym jest WP REST API i dlaczego to attack surface?
REST API WordPress zostało wprowadzone w wersji 4.4 i jest aktywne domyślnie na każdej instalacji. Dostępne endpointy możesz sprawdzić:
# Sprawdź dostępne endpointy curl https://twoja-domena.pl/wp-json/ # Endpointy użytkowników (potencjalnie wrażliwe) curl https://twoja-domena.pl/wp-json/wp/v2/users # Posty i strony publiczne curl https://twoja-domena.pl/wp-json/wp/v2/posts curl https://twoja-domena.pl/wp-json/wp/v2/pages
Endpoint /wp/v2/users domyślnie zwraca loginy wszystkich użytkowników z opublikowanymi
postami — to gotowa lista do ataków brute-force na wp-login.php.
Blokada user enumeration przez REST API
Dodaj do functions.php aktywnego motywu (lub lepiej — do własnej wtyczki mu-plugins):
<?php
/**
* Blokada user enumeration przez REST API i przez ?author=N
* Dodaj do: /wp-content/mu-plugins/security-hardening.php
*/
// 1. Ukryj endpoint /wp/v2/users dla niezalogowanych
add_filter( 'rest_endpoints', function( $endpoints ) {
if ( ! is_user_logged_in() ) {
unset( $endpoints['/wp/v2/users'] );
unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
}
return $endpoints;
} );
// 2. Blokada ?author=N redirect (ujawnia login przez URL)
add_action( 'template_redirect', function() {
if ( isset( $_GET['author'] ) && ! is_user_logged_in() ) {
wp_die( 'Forbidden', 'Forbidden', [ 'response' => 403 ] );
}
} );
// 3. Usuń login z REST API responses
add_filter( 'rest_prepare_user', function( $response ) {
if ( ! is_user_logged_in() ) {
$data = $response->get_data();
unset( $data['slug'] ); // login ukryty w slug
$response->set_data( $data );
}
return $response;
} ); Wyłączenie REST API dla niezalogowanych (selektywne)
Całkowite wyłączenie REST API może zepsuć wtyczki. Bezpieczniejsze podejście — blokada tylko wrażliwych namespace:
<?php
// Blokada dostępu do REST API dla niezalogowanych
// (zostawia publiczne endpointy dla WooCommerce, CF7 itp.)
add_filter( 'rest_authentication_errors', function( $result ) {
if ( ! empty( $result ) ) {
return $result;
}
if ( ! is_user_logged_in() ) {
$route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';
// Blokuj TYLKO wrażliwe namespace
$blocked = [ '/wp/v2/users', '/wp/v2/settings' ];
foreach ( $blocked as $path ) {
if ( str_starts_with( $route, $path ) ) {
return new WP_Error(
'rest_forbidden',
'Dostęp zabroniony.',
[ 'status' => 403 ]
);
}
}
}
return $result;
} ); Rate limiting w Nginx dla /wp-json/
Dodaj rate limiting dla endpointu REST API w konfiguracji Nginx:
# /etc/nginx/nginx.conf (w bloku http {})
limit_req_zone $binary_remote_addr zone=wp_rest:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s; # /etc/nginx/sites-available/twoja-domena.conf
server {
# ...
# Rate limiting dla REST API (10 req/s, burst 20)
location /wp-json/ {
limit_req zone=wp_rest burst=20 nodelay;
limit_req_status 429;
try_files $uri $uri/ /index.php$is_args$args;
}
# Rate limiting dla wp-login.php (1 req/s, bez burst)
location = /wp-login.php {
limit_req zone=wp_login;
limit_req_status 429;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
} JWT Authentication dla REST API
JWT (JSON Web Token) umożliwia bezstanowe uwierzytelnianie dla aplikacji zewnętrznych. Zainstaluj wtyczkę JWT Auth for WP REST API:
# wp-config.php — dodaj klucz JWT define( 'JWT_AUTH_SECRET_KEY', 'twoj-dlugi-tajny-klucz-min-32-znaki' ); define( 'JWT_AUTH_CORS_ENABLE', true );
# Krok 1: Pobierz token JWT
curl -X POST https://twoja-domena.pl/wp-json/jwt-auth/v1/token \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"haslo"}'
# Odpowiedź:
# { "token": "eyJ0eXAiOiJKV1QiLC...", "user_email": "...", "user_display_name": "..." }
# Krok 2: Użyj tokena do autoryzowanych zapytań
curl -X GET https://twoja-domena.pl/wp-json/wp/v2/users \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLC..." Application Passwords (WordPress 5.6+)
Wbudowana alternatywa dla JWT — bez dodatkowej wtyczki. Generujesz hasło per aplikacja w profilu użytkownika (Użytkownicy → Twój profil → Hasła aplikacji):
# Użycie Application Password (Basic Auth) # Format: "username:application_password" zakodowany w base64 curl -X GET https://twoja-domena.pl/wp-json/wp/v2/users/me \ -H "Authorization: Basic $(echo -n 'admin:xxxx xxxx xxxx xxxx' | base64)" # Lub wprost (mniej bezpieczne — hasło w historii bash) curl -u "admin:xxxx xxxx xxxx xxxx" \ https://twoja-domena.pl/wp-json/wp/v2/posts
Uwaga: Application Passwords wymagają HTTPS — WordPress automatycznie odrzuca żądania przez HTTP.
Fail2ban — blokada brute-force przez REST API
# /etc/fail2ban/filter.d/wordpress-rest.conf
[Definition]
failregex = ^<HOST> .* "POST /wp-json/jwt-auth/v1/token.*" (401|403|429)
^<HOST> .* "GET /wp-json/wp/v2/users.*" (403|429)
ignoreregex = # /etc/fail2ban/jail.local [wordpress-rest] enabled = true filter = wordpress-rest logpath = /var/log/nginx/access.log maxretry = 5 findtime = 300 bantime = 3600
# Restart i weryfikacja fail2ban systemctl restart fail2ban fail2ban-client status wordpress-rest
Nagłówki CORS
Jeśli REST API jest używane przez aplikację frontendową z innej domeny, skonfiguruj CORS w Nginx (zamiast polegać na domyślnych ustawieniach WordPress):
location /wp-json/ {
# Ogranicz CORS do zaufanych domen
add_header Access-Control-Allow-Origin "https://twoja-app.pl" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
if ($request_method = OPTIONS) {
return 204;
}
try_files $uri $uri/ /index.php$is_args$args;
}