Cum rezolvi

Pașii de rezolvare pentru problemele raportate de WP Monitor. Fă un backup complet (fișiere și bază de date) înainte de orice modificare. Regulile de server sunt date pentru Apache (în fișierul .htaccess din rădăcina site-ului; LiteSpeed citește același fișier) și pentru nginx (în configurația site-ului; reîncarcă nginx după aceea).

Actualizează WordPress

Un WordPress neactualizat este cea mai frecventă cale prin care site-urile sunt sparte. Corecturile de securitate apar doar pentru versiunea curentă, așa că o versiune cu o breșă cunoscută trebuie actualizată chiar în aceeași zi.

Cu acces shell, WP-CLI face același lucru din folderul site-ului:

WP-CLI
wp core update
wp core update-db
Înapoi sus

Actualizează pluginurile și temele

Pluginurile și temele sunt cealaltă cale frecventă de atac: majoritatea alertelor de securitate WordPress sunt despre ele.

WP-CLI
wp plugin update --all
wp theme update --all
Înapoi sus

Activează cache-ul de pagină

Fără cache de pagină, WordPress construiește fiecare pagină din PHP și baza de date la fiecare vizită. Un cache de pagină salvează pagina gata construită și o livrează direct – de obicei de câteva ori mai repede și mult mai rezistent la vârfuri de trafic.

Folosește un singur plugin de cache de pagină. Unele firme de găzduire (SiteGround, Kinsta, WP Engine și altele) păstrează deja paginile în cache pe server; atunci nu e nevoie de un plugin pentru asta.

Înapoi sus

Activează compresia

Compresia (Gzip sau Brotli) face paginile, CSS-ul și JavaScript-ul cu 60–80% mai mici pe drumul spre vizitator. Pluginurile de cache de mai sus o pot porni, sau se setează în configurația serverului:

Apache (.htaccess)
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml
  AddOutputFilterByType DEFLATE application/javascript text/javascript application/json
  AddOutputFilterByType DEFLATE application/xml application/rss+xml image/svg+xml
</IfModule>
nginx
# in the http { } or server { } block
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types text/plain text/css text/xml application/javascript text/javascript
           application/json application/xml application/rss+xml image/svg+xml;
Înapoi sus

Cache în browser pentru CSS și JavaScript

Când CSS-ul, JavaScript-ul, imaginile și fonturile sunt trimise cu o durată lungă de cache, vizitatorii care revin și fiecare pagină următoare le iau din browser în loc să le descarce din nou. WordPress adaugă acestor fișiere o versiune (?ver=...) care se schimbă când se schimbă fișierul, deci o durată lungă e sigură.

Majoritatea pluginurilor de cache au o opțiune „browser caching”. Sau setează pe server:

Apache (.htaccess)
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType text/javascript "access plus 1 month"
  ExpiresByType application/javascript "access plus 1 month"
  ExpiresByType image/webp "access plus 1 month"
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/svg+xml "access plus 1 month"
  ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
nginx
# in the server { } block
location ~* \.(?:css|js|webp|jpe?g|png|gif|svg|ico|woff2?)$ {
    expires 30d;
    access_log off;
    try_files $uri =404;
}

nginx: un bloc location cu propriile linii add_header nu mai moștenește liniile add_header din blocul server, așa că repetă acolo header-ele de securitate dacă adaugi header-e în el.

Înapoi sus

Grăbește primul răspuns

Este timpul până când pagina începe să sosească, măsurat de pe serverul nostru. Peste 1,5 secunde înseamnă de obicei că pagina e construită de la zero la fiecare vizită.

Înapoi sus

Repară certificatul SSL

Când certificatul nu e valid pentru adresă, vizitatorii văd un avertisment al browserului pe toată pagina („Conexiunea nu este privată”) în loc de site, motoarele de căutare îl scot din rezultate, iar formularele și autentificările nu sunt sigure. Ce faci depinde de ce am găsit:

Pe găzduire partajată folosește panoul de control: AutoSSL în cPanel, „SSL It!” sau Let’s Encrypt în Plesk, sau cere firmei de găzduire. Pe serverul propriu, certbot obține și reînnoiește certificate Let’s Encrypt:

certbot
# free Let's Encrypt certificate, renewed automatically (run as root)
certbot --nginx -d example.com -d www.example.com     # nginx
certbot --apache -d example.com -d www.example.com    # Apache

După ce HTTPS funcționează, redirecționează http:// către https://:

Apache (.htaccess)
# .htaccess, above the # BEGIN WordPress block
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
nginx
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
Înapoi sus

Dezactivează XML-RPC

xmlrpc.php este o interfață veche de acces la distanță. Atacatorii o folosesc pentru a încerca sute de parole într-o singură cerere. Majoritatea site-urilor nu mai au nevoie de ea; pluginul Jetpack și unele aplicații de publicare au, așa că sari peste acest pas dacă le folosești.

Apache (.htaccess)
<Files "xmlrpc.php">
  Require all denied
</Files>
nginx
location = /xmlrpc.php {
    deny all;
}

Și pluginurile de securitate (Wordfence, Solid Security și altele) au o opțiune pentru dezactivarea XML-RPC.

Înapoi sus

Elimină tag-ul generator WordPress

Implicit, WordPress își scrie versiunea exactă în fiecare pagină: <meta name="generator" content="WordPress 7.1.2">. Asta le spune atacatorilor ce breșe cunoscute să încerce.

Elimină-l cu acest cod sau cu opțiunea „ascunde versiunea WordPress” dintr-un plugin de securitate (Wordfence, Solid Security și altele au una):

PHP
// functions.php of a child theme, or a small custom plugin
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
Înapoi sus

Blochează accesul la readme.html

Orice instalare WordPress are un fișier readme.html în rădăcina site-ului, care afișează versiunea WordPress oricui îl deschide.

Ștergerea nu ține: WordPress îl pune la loc la fiecare actualizare. Blochează în schimb accesul la el pe server; regula rămâne și după actualizări.

Apache și LiteSpeed: adaugă asta în fișierul .htaccess din rădăcina site-ului (folderul cu wp-config.php), deasupra sau sub blocul # BEGIN WordPress, nu în interiorul lui (WordPress rescrie acel bloc):

Apache (.htaccess)
<Files "readme.html">
  Require all denied
</Files>

nginx: adaugă asta în blocul server { } al site-ului, apoi verifică și reîncarcă configurația (nginx -t, apoi systemctl reload nginx). O potrivire exactă location = are prioritate față de celelalte reguli:

nginx
location = /readme.html {
    deny all;
}
Înapoi sus

Nu mai lista numele de utilizator

Implicit, /wp-json/wp/v2/users listează numele de utilizator ale tuturor celor care au publicat conținut – jumătate din datele de autentificare, oferite atacatorilor. Acest cod ascunde lista de vizitatorii nelogați, iar editorul de blocuri funcționează în continuare:

PHP
// functions.php of a child theme, or a small custom plugin
add_filter('rest_endpoints', function ($endpoints) {
    if (!is_user_logged_in()) {
        unset($endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)']);
    }
    return $endpoints;
});

Majoritatea pluginurilor de securitate au o opțiune similară („disable user enumeration”).

Înapoi sus

Dezactivează listarea directoarelor

Cu listarea directoarelor pornită, oricine poate vedea lista fișierelor dintr-un folder precum /wp-content/uploads/, inclusiv fișiere private și backup-uri.

Apache (.htaccess)
Options -Indexes
nginx
# nginx lists directories only where autoindex is switched on: remove it, or set
autoindex off;
Înapoi sus

Elimină jurnalul de debug public

wp-content/debug.log poate conține căi de pe server, erori de pluginuri și uneori detalii despre baza de date, iar oricine îl poate descărca. Șterge fișierul acum, apoi oprește WordPress să-l mai scrie acolo:

wp-config.php
// wp-config.php
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
// or, to keep logging, write the log outside the website folder:
// define('WP_DEBUG_LOG', '/home/USER/logs/wp-debug.log');

Și blochează numele fișierului, în caz că jurnalizarea e pornită din nou mai târziu:

Apache (.htaccess)
<Files "debug.log">
  Require all denied
</Files>
nginx
location ~* /debug\.log$ {
    deny all;
}
Înapoi sus

Ascunde versiunea Apache, nginx și PHP

Header-e de răspuns precum Server: nginx/1.30.5 sau X-Powered-By: PHP/8.2.12 le spun atacatorilor exact pentru ce versiuni să caute breșe cunoscute. Doar numele serverului (nginx, Apache) nu e o problemă; numărul versiunii trebuie ascuns. Asta nu înlocuiește actualizările, doar oprește afișarea versiunii.

Apache: în configurația principală a serverului (nu se poate în .htaccess). Pe găzduire partajată întreabă firma de găzduire; majoritatea fac deja asta.

Apache
# main Apache configuration (not .htaccess), e.g. /etc/apache2/conf-enabled/security.conf
# or /etc/httpd/conf/httpd.conf, then reload Apache
ServerTokens Prod
ServerSignature Off

nginx: în blocul http { }. fastcgi_hide_header elimină și header-ul cu versiunea PHP:

nginx
# in the http { } block of nginx.conf, then: nginx -t && systemctl reload nginx
server_tokens off;
# hides the PHP version header coming from PHP-FPM
fastcgi_hide_header X-Powered-By;

PHP: oprește PHP să mai trimită X-Powered-By. expose_php se poate seta doar în php.ini sau în pool-ul PHP-FPM, nu în .user.ini:

php.ini
; php.ini (or the PHP-FPM pool: php_admin_flag[expose_php] = off), then restart PHP-FPM
expose_php = Off

Fără acces la php.ini (găzduire partajată pe Apache sau LiteSpeed), elimină header-ul din .htaccess:

Apache (.htaccess)
# .htaccess: removes the PHP version header when you cannot change php.ini
<IfModule mod_headers.c>
  Header always unset X-Powered-By
  Header unset X-Powered-By
</IfModule>
Înapoi sus

Adaugă header-e de securitate

Trei header-e de răspuns blochează atacuri frecvente din browser:

Apache (.htaccess)
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set X-Content-Type-Options "nosniff"
</IfModule>
nginx
# in the server { } block that serves HTTPS
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
Înapoi sus

Creează-ți contul gratuit

Monitorizează gratuit până la 5 site-uri WordPress.

Vei primi un email de confirmare. Prin crearea contului accepți Termeni și condiții și Politica de confidențialitate.