.htaccess

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).

Configuraciones en bloques concretos

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.

# BEGIN HTTPS via Proxy
SetEnvIf X-Forwarded-Proto https HTTPS=on
# END HTTPS via Proxy

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.

# BEGIN ModPagespeed
<IfModule pagespeed_module>
  ModPagespeed off
</IfModule>
# END ModPagespeed

Bloqueo de los .ht<loquesea>

Por defecto bloquearemos los accesos externos a los ficheros de configuración, y este mismo.

# START - [Seguridad] Bloqueado el acceso a ficheros ".ht" y ".ftp"
<FilesMatch "^\.([Hh][Tt])">
  Deny from all
</FilesMatch>
<FilesMatch "^\.([Ff][Tt][Pp])">
  Deny from all
</FilesMatch>
# END

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.

# BEGIN Directory browsing
<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>
# END Directory browsing

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.

En este primer bloque, configuramos el dominio principal y realizamos / forzamos su uso.

# [Seguridad] Configuracion del dominio canonico
RewriteCond %{HTTP_HOST} !subdomain\.example\.com
RewriteRule (.*) https://subdomain.example.com%{REQUEST_URI} [L,R=308]

Posteriormente verificamos si está o no activado el HTTPS y, si no se está haciendo la petición, se fuerza.

RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=308]

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.

# [Seguridad] Bloqueamos la peticiones TRACE y TRACK
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.

# [Seguridad] Ficheros estáticos generales
RewriteRule readme\.(html|txt) - [L,R=404]
RewriteRule (licencia|license|LICENSE)\.(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).

# [Seguridad] Ficheros propios de WordPress
RewriteRule ^wp-config - [L,R=404]
RewriteRule ^wp-cron\.php - [L,R=404]
RewriteRule ^wp-admin/(install|setup-config)\.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.

# [Seguridad] Bloqueo del listing de usuarios
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.

# [Seguridad] Bloqueo de listados de carpetas
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.

# [Seguridad] Bloqueo de ficheros inseguros
RewriteRule ^wp-content/uploads/.+\.(html|js|php|shtml|swf)$ - [L,R=403]
RewriteRule ^wp-content/plugins/.+\.(css\.map|js\.map|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|webm|webp|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.

# [Seguridad] Otros bloqueos
RewriteRule ^sftp-config.json - [L,R=404]
RewriteRule (access|error)_log - [L,R=404]
RewriteRule (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]

Acceso abierto al AJAX del administrador

Independientemente de las decisiones que se tomen de bloqueos, el sistema de acceso de AJAX, que puede usarse desde el frontal del sitio.

# START - [Funcionalidad] Dejamos acceso abierto al AJAX del administrador
<FilesMatch "wp-admin/admin-ajax\.php">
  Allow from all
</FilesMatch>
# END

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.

# START - [Seguridad] Mitigation CVE-2018-6389
<FilesMatch "load-(scripts|styles)\.php">
  Deny from all
</FilesMatch>
# END

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.

### 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

Control simple de ataques XSS

Evita ataques muy sencillos de cross-scripting mediante la URL.

### 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

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 ^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).

# BEGIN Strict-Transport-Security
<IfModule mod_headers.c>
  Header always set Content-Security-Policy: upgrade-insecure-requests;
  Header always set Strict-Transport-Security "max-age=10886400; includeSubDomains; preload"
  Header always set X-XSS-Protection "1; mode=block"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "origin-when-cross-origin"
</IfModule>
# END Strict-Transport-Security

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 "^\.([Hh][Tt])">
  Deny from all
</FilesMatch>
# END
# BEGIN Directory browsing
<IfModule mod_autoindex.c>
  Options -Indexes
</IfModule>
# END Directory browsing
# START Expresión regular de WordPress
<IfModule mod_rewrite.c>
  RewriteEngine On
  # [Seguridad] Configuracion del dominio canonico
  RewriteCond %{HTTP_HOST} !subdomain\.example\.com
  RewriteRule (.*) https://subdomain.example.com%{REQUEST_URI} [L,R=308]
  RewriteCond %{HTTPS} !on
  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L,R=308]
  # [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)\.(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)\.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/.+\.(html|js|php|shtml|swf)$ - [L,R=403]
  RewriteRule ^wp-content/plugins/.+\.(css\.map|js\.map|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|webm|webp|wmv|xlsx?|zip) - [L,R=404]
  # [Seguridad] Otros bloqueos
  RewriteRule ^sftp-config.json - [L,R=404]
  RewriteRule (access|error)_log - [L,R=404]
  RewriteRule (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ - [L,R=404]
</IfModule>
# END
# START - [Funcionalidad] Dejamos acceso abierto al AJAX del administrador
<FilesMatch "wp-admin/admin-ajax\.php">
  Allow from all
</FilesMatch>
# END
# START - [Seguridad] Mitigation CVE-2018-6389
<FilesMatch "load-(scripts|styles)\.php">
  Deny from all
</FilesMatch>
# END
# BEGIN WordPress
<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /
  ### 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 Strict-Transport-Security
<IfModule mod_headers.c>
  Header always set Content-Security-Policy: upgrade-insecure-requests;
  Header always set Strict-Transport-Security "max-age=10886400; includeSubDomains; preload"
  Header always set X-XSS-Protection "1; mode=block"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "origin-when-cross-origin"
</IfModule>
# END Strict-Transport-Security