Última revisión: 2 de octubre de 2021
Si utilizas Apache HTTPD o LiteSpeed o cualquier sistema basado en estos servidores web quizá te interese configurar el fichero .htaccess de la raíz de la configuración de tu WordPress de una manera compleja (más de la que viene por defecto).
- Configuración detallada por bloques
- Ficheros por defecto
- Dominio canónico
- Bloqueo de peticiones «raras»
- Bloqueo de accesos a ficheros con datos
- Ficheros importantes de WordPress
- Bloqueo de listado de usuarios
- Bloqueo de listados de carpetas
- Bloqueo de ficheros «inseguros»
- Otros bloqueos
- Mitigation CVE-2018-6389
- Bloqueo del XML-RPC
- Bloqueo de la ejecución de PHP en los «uploads»
- Control de los wp-includes
- Control simple de ataques XSS
- Código por defecto de WordPress
- Mejorar las peticiones inseguras
- Caché de estáticos
- Compresión de los contenidos
- Contenido completo del .htaccess
- Seguir con Seguridad para WordPress
- Sobre este documento
Configuración detallada por bloques
HTTPS via Proxy
Es posible que haya un proxy por delante de tu sitio web, por lo que si es así, debemos informar al sistema para decirle que ya está activado y que no ejecute determinados elementos.
SetEnvIf X-Forwarded-Proto https HTTPS=on
Módulo PageSpeed
Aunque puede ser una buena solución en determinados casos, por defecto configuraremos el PageSpeed Module de forma inactiva por defecto ya que habrá varias configuraciones incluidas que hace este módulo en nuestra configuración.
<IfModule pagespeed_module>
ModPagespeed off
</IfModule>
Bloqueo de ficheros de servidor
Por defecto bloquearemos los accesos externos a los ficheros de configuración, y este mismo.
<FilesMatch "^\.ht">
Deny from all
</FilesMatch>
<FilesMatch "^\.ftp">
Deny from all
</FilesMatch>
<FilesMatch "^php.ini$">
Deny from all
</FilesMatch>
<FilesMatch "^\.well-known">
Allow from all
</FilesMatch>
Activar el CORS para algunos tipos
En algunos casos los ficheros de fuentes necesitan ser llamados desde distintas
<FilesMatch "\.(?:ttf|eot|woff|otf)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
Desactivar el listado de directorios
Algunos sistemas por defecto permiten que al acceder a una carpeta que no tenga un fichero de ejecución por defecto muestre todos los contenidos. Si este es el caso, podemos desactivar esta opción por defecto.
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
Ficheros por defecto
Por defecto configuraremos que el servidor web haga llamadas al index.php, y en última instancia, que llame al fichero principal de WordPress. Además de decirle que siga y use los SymLinks.
DirectoryIndex index.php index.html /index.php
Options None
Options FollowSymLinks
ServerSignature Off
Dominio canónico
Aunque WordPress gestiona bien las redirecciones y los dominios principales, en los casos en los que solo utilicemos un dominio, lo mejor es hacer esa gestión controlada desde el .htaccess. En este caso configuramos tres elementos.
Configuramos el dominio principal y realizamos / forzamos su uso. Posteriormente verificamos si está o no activado el HTTPS y, si no se está haciendo la petición, se fuerza.
RewriteCond %{HTTP_HOST} !www\.example\.com
RewriteRule (.*) https://www.example.com%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Bloqueo de peticiones «raras»
Por norma general suelen realizarse peticiones GET y POST, incluso HEAD. Otros tipos de peticiones suelen ser extrañas y en muy pocos casos serán necesarias para el uso de WordPress.
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]
Bloqueo de accesos a ficheros con datos
Algunos ficheros habituales en WordPress incluyen información concreta sobre versiones y algunos datos. Gracias a este código bloqueamos los accesos a ficheros de lectura o de licencias.
RewriteRule readme\.(html|txt) - [L,R=404]
RewriteRule (licencia|license|LICENSE|olvasdel|lisenssi|liesmich)\.(html|txt) - [L,R=404]
Ficheros importantes de WordPress
Existen algunos ficheros que por la información que contienen o por su falta de uso puede ser interesenta bloquear. El principal es el wp-config que incluye mucha información. Otros son el wp-cron (que se recomienda ejecutar mediante un administrador de tareas, los ficheros de instalación de WordPress (una vez hecha la instalación estos no son necesarios ejecutar) y un fichero histórico de gestión de enlaces (que la mayoría de instalaciones no utilizan).
RewriteRule ^wp-config - [L,R=404]
RewriteRule ^wp-cron\.php - [L,R=404]
RewriteRule ^wp-admin/(install|setup-config|upgrade)\.php - [L,R=404]
RewriteRule ^wp-admin/maint/repair\.php - [L,R=404]
RewriteRule ^wp-links-opml\.php$ - [L,R=404]
Bloqueo de listado de usuarios
Aunque no es un elemento grave que se sepa la lista de usuarios de un sitio, en algunos casos es posible que no se quiera tener acceso a las fichas o páginas de la lista de usuarios. Para evitar esto podemos bloquear los accesos habituales que incluye el propio sistema de enlaces permanentes.
RewriteCond %{QUERY_STRING} ^author= [NC]
RewriteRule .* - [F,L]
RewriteRule ^author/ - [F,L]
Bloqueo de listados de carpetas
Una de las formas de detección de la existencia de elementos (plugins, themes…) es la detección de la existencia de carpetas. Para que las herramientas de pentesting tengan más problemas, es mejor aplicar un código 404.
RewriteRule ^wp-content/mu-plugins/$ - [L,R=404]
RewriteRule ^wp-content/(plugins|themes)/(.+)/$ - [L,R=404]
Bloqueo de ficheros «inseguros»
En algunas carpetas existen una serie de elementos que, por norma general, no tienen porqué estar o ejecutarse.
RewriteRule ^wp-content/(?:uploads|files)/.+\.(html|js|php|shtml|swf)$ - [L,R=403]
RewriteRule ^wp-content/plugins/.+\.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) - [L,R=404]
Otros bloqueos
Algunos ficheros generales de configuración, de logs y similares que por norma general no tienen que ser accesibles desde fuera.
RewriteRule ^sftp-config.json - [L,R=404]
RewriteRule (access|error)_log - [L,R=404]
RewriteRule installer-log\.txt - [L,R=404]
RewriteRule wp-content/debug\.log - [L,R=404]
RewriteRule (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]
Mitigation CVE-2018-6389
Por norma general es mejor bloquear el posible ataque que se puede llevar a cabo de forma sencilla con la carga de todos los scripts. Para evitarlo es mejor bloquear su carga.
<FilesMatch "load-(scripts|styles)\.php">
Deny from all
</FilesMatch>
Bloqueo del XML-RPC
Si no usas esta tecnología es mejor bloquearla e impedir ataques o accesos inconvenientes. Suele usarse con la App de WordPress o para realizar Pingbacks.
<FilesMatch "xmlrpc\.php">
Deny from all
</FilesMatch>
Bloqueo de la ejecución de PHP en los «uploads»
En la carpeta de subida de ficheros no debería haber ficheros PHP, pero es un lugar donde muchos ataques se producen. En este caso bloquearemos su ejecución.
<FilesMatch "wp-content/uploads/(.+)\.php">
Deny from all
</FilesMatch>
Control de los wp-includes
En la carpeta de los includes suelen haber elementos que no suelen ejecutarse de forma externa, y por esto, en general, es mejor bloquear su ejecución.
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
Control simple de ataques XSS
Evita ataques muy sencillos de cross-scripting mediante la URL.
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
Un detalle importante es que si dentro del panel gestionas códigos de publicidad u otros tipos de scripts, este sistema puede ser muy agresivo según la segunda línea (la que incluye los scripts), por lo que se podría eliminar.
Código por defecto de WordPress
Integramos el código que nos recomienda y suele generar el propio WordPress.
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
Mejorar las peticiones inseguras
Con esto todas las peticiones internas que se hagan del sitio desde HTTP pasarán automáticamente a HTTPS. Además se añaden otros sistemas para forzar que la navegación se haga exclusivamente por HTTPS (HSTS).
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy: upgrade-insecure-requests;
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header unset Pragma
Header always unset WP-Super-Cache
Header always unset X-Pingback
</IfModule>
Caché de estáticos
Podemos activar una caché similar a la que realizan las CDN en los navegadores de los usuarios.
<IfModule mod_expires.c>
ExpiresActive on
#Varios
ExpiresByType application/pdf A2592000
ExpiresByType image/x-icon A2592000
ExpiresByType image/vnd.microsoft.icon A2592000
ExpiresByType image/svg+xml A2592000
#Imagenes
ExpiresByType image/jpg A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresByType image/gif A2592000
ExpiresByType image/webp A2592000
#Media
ExpiresByType video/ogg A2592000
ExpiresByType audio/ogg A2592000
ExpiresByType video/mp4 A2592000
ExpiresByType video/webm A2592000
#CSS/JS
ExpiresByType text/css A2592000
ExpiresByType text/javascript A2592000
ExpiresByType application/javascript A2592000
ExpiresByType application/x-javascript A2592000
#Fuentes
ExpiresByType application/x-font-ttf A2592000
ExpiresByType application/x-font-woff A2592000
ExpiresByType application/font-woff A2592000
ExpiresByType application/font-woff2 A2592000
ExpiresByType application/vnd.ms-fontobject A2592000
ExpiresByType font/ttf A2592000
ExpiresByType font/woff A2592000
ExpiresByType font/woff2 A2592000
</IfModule>
Compresión de los contenidos
Podemos reducir el tamaño de los ficheros que se pueden comprimir (principalmente textos).
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/ld+json
AddOutputFilterByType DEFLATE application/manifest+json
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/schema+json
AddOutputFilterByType DEFLATE application/vnd.geo+json
AddOutputFilterByType DEFLATE application/x-web-app-manifest+json
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/eot
AddOutputFilterByType DEFLATE image/bmp
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE image/vnd.microsoft.icon
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/x-component
AddOutputFilterByType DEFLATE text/xml
</IfModule>
Contenido completo del .htaccess
El fichero completo quedaría de la siguiente manera:
# BEGIN HTTPS via Proxy
SetEnvIf X-Forwarded-Proto https HTTPS=on
# END HTTPS via Proxy
# BEGIN ModPagespeed
<IfModule pagespeed_module>
ModPagespeed off
</IfModule>
# END ModPagespeed
# START - [Seguridad] Bloqueado el acceso a ficheros ".ht"
<FilesMatch "^\.ht">
Deny from all
</FilesMatch>
<FilesMatch "^\.ftp">
Deny from all
</FilesMatch>
<FilesMatch "^php.ini$">
Deny from all
</FilesMatch>
<FilesMatch "^\.well-known">
Allow from all
</FilesMatch>
# END
# BEGIN cors
<FilesMatch "\.(?:ttf|eot|woff|otf)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
#END
# BEGIN Directory browsing
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# END Directory browsing
DirectoryIndex index.php index.html /index.php
Options None
Options FollowSymLinks
ServerSignature Off
# START Expresión regular de WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
# [Seguridad] Configuracion del dominio canonico
RewriteCond %{HTTP_HOST} !www\.example\.com
RewriteRule (.*) https://www.example.com%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# [Seguridad] Bloqueamos la peticiones TRACE y TRACK
RewriteCond %{REQUEST_METHOD} ^(TRACE|TRACK)
RewriteRule .* - [F]
# [Seguridad] Ficheros estáticos generales
RewriteRule readme\.(html|txt) - [L,R=404]
RewriteRule (licencia|license|LICENSE|olvasdel|lisenssi|liesmich)\.(html|txt) - [L,R=404]
# [Seguridad] Ficheros propios de WordPress
RewriteRule ^wp-config - [L,R=404]
RewriteRule ^wp-cron\.php - [L,R=404]
RewriteRule ^wp-admin/(install|setup-config|upgrade)\.php - [L,R=404]
RewriteRule ^wp-admin/maint/repair\.php - [L,R=404]
RewriteRule ^wp-links-opml\.php$ - [L,R=404]
# [Seguridad] Bloqueo del listing de usuarios
RewriteCond %{QUERY_STRING} ^author= [NC]
RewriteRule .* - [F,L]
RewriteRule ^author/ - [F,L]
# [Seguridad] Bloqueo de listados de carpetas
RewriteRule ^wp-content/mu-plugins/$ - [L,R=404]
RewriteRule ^wp-content/(plugins|themes)/(.+)/$ - [L,R=404]
# [Seguridad] Bloqueo de ficheros inseguros
RewriteRule ^wp-content/(?:uploads|files)/.+\.(html|js|php|shtml|swf)$ - [L,R=403]
RewriteRule ^wp-content/plugins/.+\.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) - [L,R=404]
# [Seguridad] Otros bloqueos
RewriteRule ^sftp-config.json - [L,R=404]
RewriteRule (access|error)_log - [L,R=404]
RewriteRule installer-log\.txt - [L,R=404]
RewriteRule wp-content/debug\.log - [L,R=404]
RewriteRule (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]
</IfModule>
# END
# START - [Seguridad] Mitigation CVE-2018-6389
<FilesMatch "load-(scripts|styles)\.php">
Deny from all
</FilesMatch>
# END
# START - [Seguridad] XML-RPC
<FilesMatch "xmlrpc\.php">
Deny from all
</FilesMatch>
# END
# START - [Seguridad] No ejecutar ficheros en Uploads
<FilesMatch "wp-content/uploads/(.+)\.php">
Deny from all
</FilesMatch>
# END
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
### START WP includes
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
### END WP includes
### START SQL Injection
RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule ^(.*)$ index.php [F,L]
### END SQL Injection
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# BEGIN Security Headers
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy: upgrade-insecure-requests;
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header unset Pragma
Header always unset WP-Super-Cache
Header always unset X-Pingback
</IfModule>
# END Strict-Transport-Security
# BEGIN EXPIRES
<IfModule mod_expires.c>
ExpiresActive on
#Varios
ExpiresByType application/pdf A2592000
ExpiresByType image/x-icon A2592000
ExpiresByType image/vnd.microsoft.icon A2592000
ExpiresByType image/svg+xml A2592000
#Imagenes
ExpiresByType image/jpg A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresByType image/gif A2592000
ExpiresByType image/webp A2592000
#Media
ExpiresByType video/ogg A2592000
ExpiresByType audio/ogg A2592000
ExpiresByType video/mp4 A2592000
ExpiresByType video/webm A2592000
#CSS/JS
ExpiresByType text/css A2592000
ExpiresByType text/javascript A2592000
ExpiresByType application/javascript A2592000
ExpiresByType application/x-javascript A2592000
#Fuentes
ExpiresByType application/x-font-ttf A2592000
ExpiresByType application/x-font-woff A2592000
ExpiresByType application/font-woff A2592000
ExpiresByType application/font-woff2 A2592000
ExpiresByType application/vnd.ms-fontobject A2592000
ExpiresByType font/ttf A2592000
ExpiresByType font/woff A2592000
ExpiresByType font/woff2 A2592000
</IfModule>
# END EXPIRES
# BEGIN HttpHeadersCompression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/ld+json
AddOutputFilterByType DEFLATE application/manifest+json
AddOutputFilterByType DEFLATE application/rdf+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/schema+json
AddOutputFilterByType DEFLATE application/vnd.geo+json
AddOutputFilterByType DEFLATE application/x-web-app-manifest+json
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/eot
AddOutputFilterByType DEFLATE image/bmp
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE image/vnd.microsoft.icon
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/x-component
AddOutputFilterByType DEFLATE text/xml
</IfModule>
# END HttpHeadersCompression
Seguir con Seguridad para WordPress
Actual
Sobre este documento
Este documento está regulado por la licencia EUPL v1.2, publicado en WP SysAdmin y creado por Javier Casares. Por favor, si utilizas este contenido en tu sitio web, tu presentación o cualquier material que distribuyas, recuerda hacer una mención a este sitio o a su autor, y teniendo que poner el material que crees bajo licencia EUPL.