WordPress.NewCitizen.io

The second part of a 3 part WordPress Comprehensive Security Guide, covering all aspects of locking down WordPress websites.

Most Comprehensive WordPress Security Guide-Part 2/3

WordPress Security Guide Part 2

The .htaccess file often contains a section delimited by # BEGIN WordPress and # END WordPress. This section is maintained by WordPress itself, and it’s a good idea not to modify anything between these tags because they can be overwritten by WordPress updates or permalink structure changes.

For custom rules, you should add them either above the # BEGIN WordPress line or below the # END WordPress line to ensure they remain intact.

Here are some Apache .htaccess code snippets that can be employed to implement robust security measures. Depending on your specific setup, goals and needs, you can choose to utilize some or most of these code snippets.

1. Disable directory browsing

Significance of Blocking Directory Browsing:

  1. Security: Directory listings can expose files that weren’t meant to be public. For example, backup files, configuration files, or other sensitive information might inadvertently be revealed. By blocking directory browsing, you can reduce the risk of exposing such files.
  2. Privacy: Even if files aren’t strictly sensitive, you might not want to show the entire structure of your site or application to the public.
  3. Professionalism: Leaving directory listing enabled on a professional or commercial site might be perceived as a sign of inattention to detail or as a lack of concern about security and user experience.

In general, unless there’s a specific reason to enable directory listings (e.g., a public file repository), it’s a good practice to disable it to enhance security and privacy.

Explanation: The -Indexes option ensures that if a directory does not have an index file (like index.php or index.html), the contents of that directory will not be displayed.

This disables directory browsing, so if someone tries to access a directory without an index file (like index.html), they’ll receive a “403 Forbidden” error.

1.1 Disabling Directory Browsing:

Directory browsing, also known as directory listing, is a feature of web servers that allows a user to see a list of all files and directories in a specific website directory when there isn’t an index file (e.g., index.html or index.php) in that directory. This can be seen as a simple file manager interface via the web browser.

Purpose of Directory Browsing:

  1. Default Behavior: In many web servers, directory browsing is enabled by default as a simple way to provide access to files. If an administrator hasn’t put an index file in a directory, the web server will just show a list of files as a fallback.

  2. Ease of Access: It provides easy access to files in a directory without the need for creating an index or dashboard page.

  3. Collaboration and Sharing: In controlled environments, directory browsing can be used as a basic way to share files among a group without needing additional software.

Why Should We Block Directory Browsing:

  1. Security Concern: Exposing the directory structure and files can give potential attackers information about your website’s structure, files, plugins, themes, etc. This information can be used to discover vulnerabilities specific to the software versions you’re using.

  2. Privacy Concern: Even if the files aren’t sensitive, you might not want visitors to see every file or directory on your site, especially if they contain personal or unreleased data.

  3. Professional Appearance: Directory listings don’t look professional to users. If someone stumbles upon a directory without an index file, it’s better to show a custom error page or redirect them to another part of the site rather than displaying a raw list of files.

  4. Preventing Accidental Data Exposure: There might be instances where data or backup files are unintentionally placed in directories. If directory browsing is disabled, these files are less likely to be accidentally exposed to the public.

  5. Resource Usage: Frequent crawling of directory listings by automated bots or malicious actors can use unnecessary server resources.


# Disable directory browsing 
# Block Directory Browsing
Options -Indexes

1.2 Enable directory browsing


Options All +Indexes

1.3 Enable directory browsing, disable file browsing


# Enable directory browsing, disable file browsing 
Options All +Indexes 
IndexIgnore *

1.4 Enable directory browsing, disable specific files


# Enable directory browsing, disable specific files 
Options All +Indexes 
IndexIgnore *.wmv *.mp4 *.avi *.etc

2 Disable listing of sensitive files


# Examples of Sensitive Files:
# Configuration Files:
- .env
- config.php
- wp-config.php
# Backup Files:
- backup.zip
- database.sql
- website.tar.gz
# Log Files:
- error.log
- access.log
# Private Data:
- private_data.xlsx
- user_list.csv
# Temporary Files:
- .tmp
- file.bak

# Disable listing of sensitive files 
IndexIgnore .htaccess .??* *~ *# HEADER* README* _vti* RCS CVS *,v *,t

2.1 Prevent access to a specific file

Disabling directory browsing does not prevent access to files if the exact URL is known or guessed. This means that if someone knows or can guess the filename, they can still access it directly.

For example, consider a scenario where directory browsing is disabled, but a backup file named database-backup.sql is in the web root. If an attacker guesses or finds out about this file (perhaps via other means such as references in error logs, or simply by trying common filenames), they can access it directly using http://example.com/database-backup.sql, even if they can’t see it in a directory listing.


<Files "database-backup.sql">
    Require all denied
</Files>

2.2 Prevent access to several specific files

The Files directive also supports regular expressions. To restrict access to multiple files, modify the example below by replacing it with your specific filenames:

FYI: The backslash before the dot in database-backup\.sql and config\.php is used to escape the dot, ensuring it’s interpreted as a literal dot character rather than the regex special character that matches any single character.


<FilesMatch "^(database-backup\.sql|config\.php|secrets\.txt)$">
    Require all denied
</FilesMatch>

2.3 Preventing access to specific types of files using .htacces


<FilesMatch "\.(htaccess|htpasswd|sql|log|bak|backup|old|orig|ini|conf|phps|fla|psd|log|sh)$">
    Require all denied
</FilesMatch>

3.0 Require SSL/HTTPS (Forcing HTTPS On Site)


# Require SSL/HTTPS 
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

4.0 Limit File Upload Size

One way to help protect your server against DOS attacks is to limit the size of file-uploads.. For this directive, file-sizes are expressed in bytes. Place this line in outside the #BeginWordPress, #EndWordPress areas in the .htaccess file:

Limit File Upload Size

Description: Limit the size of uploaded files to prevent denial of service attacks, where sometimes bad actors will upload huge files to crash/hack your site. In the example below, we are limiting file-upload size to 10 megabytes.

Please keep in mind, this code is only useful if you actually allow users to upload files to your site, say via a form or something similar.


# Limit File Upload Size
LimitRequestBody 10485760

5.0 Block trace and track Request Methods

TRACE and OPTIONS are HTTP methods are part of the HTTP/1.1 protocol as defined by the Internet Engineering Task Force (IETF) in various Request for Comments (RFC) documents, specifically RFC 2616 and its successor, RFC 7231.

When it comes to WordPress security:

  1. the TRACE method is commonly blocked due to its potential misuse in Cross-Site Tracing (XST) attacks. However, blocking other standard HTTP methods might not be beneficial since they are frequently used by WordPress core, plugins, and themes for legitimate purposes.
  2. The OPTIONS method can be used to find out which HTTP methods are allowed on a server. While this doesn’t pose a direct security threat, it can give potential attackers some insights. Blocking it can be considered, but you might want to ensure nothing breaks if you do so.

# Disable TRACE method
TraceEnable off
# Disable OPTIONS method
<Limit OPTIONS>
    Require all denied
</Limit>

6.0 Prevent hotlinking

Hotlinking: Hotlinking, often referred to as bandwidth theft, occurs when another website directly links to images (or other resources) on your site, causing the image to load from your server on their site. This means every time someone visits the offending site, your server’s bandwidth is used to display the image, which can lead to increased server costs and slower performance for your own visitors.

Hotlinking can be particularly concerning for WordPress site owners, especially if the site hosts unique images that can be reused by others without permission.

Disabling Image Hotlinking: To disable hotlinking in an Apache web server, you can use the .htaccess file. The following snippet will prevent hotlinking of images (including the .webp format):


<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]
    RewriteRule \.(jpg|jpeg|png|gif|webp)$ - [NC,F,L]
</IfModule>

6.1 Protect Against music/video/audio files being Hotlinked


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]
    RewriteRule \.(gif|jpe?g?|png|mp3|mp4|wmv|flv|avi)$ - [NC,F,L]
</IfModule>

Remember to replace yourdomain.com with your actual domain name before implementing it on your site.

6.2 Return a Specific Image for Hotlinked Images:


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]
    RewriteRule \.(gif|jpe?g?|png|webp)$ /path_to_your_image/no_hotlinking.jpg [L]
</IfModule>

Remember to replace yourdomain.com and /path_to_your_image/no_hotlinking.jpg with the appropriate values before implementing it on your site.

6.3 Redirect to a website when someone tries to Hotlink an Image


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?yourdomain.com [NC]
    RewriteRule \.(gif|jpe?g?|png|webp)$ https://www.example.com/hotlinking_notice.html [R,L]
</IfModule>

Make sure to replace  yourdomain.com and the example URL (https://www.example.com/hotlinking_notice.html) with appropriate values before implementing it on your site.

7.0 How to Directory Password protect on your website

To securely protect specific directories on your website, we’ll work with two crucial files: .htaccess and .htpasswd. Here’s a comprehensive guide:

Understanding the Files:
  1. .htaccess: Imagine this file as the virtual gatekeeper for your folder. It sends a clear message: “Authorized Personnel Only.” Position this file in the directory you want to shield.

  2. .htpasswd: This is your virtual guest book. Only those listed, along with their confidential passwords, can gain entry. For enhanced security, we store this file separately, away from potential intruders.

Quick Setup Guide:
1. Creating the Guest Registry (.htpasswd):

First, find a secure location:


cd /path/to/your/secure/place/

Initiate your registry and add a username:


htpasswd -c .htpasswd your_username

You’ll be prompted to set a password. To add more users:


htpasswd .htpasswd another_username
2. Setting Up the Entrance Sign (.htaccess):

Move to the WordPress directory you wish to secure:


cd /path/to/your/wordpress/folder/

On Handling Multiple Directories:

For custom protection levels or varied rules across directories, use a unique .htaccess file for each. Rules in a sub-directory’s .htaccess can either replace or supplement the parent’s directives, giving precise control.

To edit or create the .htaccess file:


nano .htaccess

Input this code:


<IfModule mod_authn_file.c>
AuthType Basic
AuthName "Private Area"
AuthUserFile /path/to/your/secure/place/.htpasswd
Require valid-user
</IfModule>

Make sure to adjust the AuthUserFile to where your .htpasswd is located.

Customizing the Password Prompt:

The password prompt is adaptable. In the prior code, “Private Area” is shown. To modify it to “Username and password required”, change the AuthName:


AuthName "Username and password required"

8.0 Block unauthorized users from viewing or accessing the htaccess or wp-config.php


<Files .htaccess>
    Require all denied
</Files>
<Files wp-config.php>
    Require all denied
</Files>

9.0 Protect /wp-content/ Directory

The wp-content folder is an essential directory in a WordPress installation. It holds the majority of user-generated and customizable content and functionalities. Here’s a breakdown of its purpose:

  1. Themes: The wp-content directory contains the themes folder, where all the WordPress themes (both active and inactive) are stored. Each theme has its folder within the themes directory, containing all of the PHP, CSS, JavaScript, and image files that make up the theme.

  2. Plugins: The plugins folder, also in the wp-content directory, stores all the plugins that you install to extend the functionality of WordPress. Like themes, each plugin has its directory.

  3. Uploads: The uploads folder is where all the media files (images, videos, documents, etc.) that you upload to your WordPress site are stored. They’re usually organized in a year/month folder structure.

  4. Cache & Miscellaneous: Some caching plugins and other utilities might store cached files and other data within the wp-content directory. This cache helps in speeding up your WordPress site.

  5. Languages: The languages folder contains translations and helps in making WordPress sites multilingual.

Why Protect the wp-content Directory?

  1. Security: Since wp-content contains themes and plugins, it can be a target for malicious activities. Vulnerabilities in a theme or plugin can be exploited. By protecting the directory, you make it harder for attackers to find vulnerabilities or inject malicious code.

  2. Data Integrity: If unauthorized access is granted, someone could potentially alter theme files or plugin files, which could break the website or change its appearance and functionality.

  3. Prevent Direct Access: You don’t want users or bots to access your themes, plugins, or uploaded media directly, bypassing WordPress’s built-in mechanisms.

  4. Content Protection: If you have premium content or digital products, they might be stored in the uploads folder. Protecting wp-content can prevent unauthorized access or direct downloads of such products.

  5. Performance: Preventing unauthorized or unnecessary access can help in reducing the load on the server, ensuring that your WordPress site remains fast and responsive.

Deny direct access to the wp-content directory except for the files you want to be publicly accessible.

The primary contents of wp-content that might need to be publicly accessible are:

  • Images (*.jpg, *.jpeg, *.png, *.gif, etc.)
  • Stylesheets and Scripts (*.css, *.js)
  • Videos (*.mp4, *.webm, etc.)

To secure the wp-content folder, you can place a .htaccess file within that directory with the following content:

<Files *.*>
    Require all denied
</Files>
# Allow access to the essential files.
<FilesMatch "\.(jpg|jpeg|png|gif|css|js|webm|mp4|woff|woff2|otf|ttf|svg|eot)$">
    Require all granted
</FilesMatch>

10 Block WordPress XML-RPC

The XML-RPC functionality in WordPress allows for remote connections to the site, but it’s also known to be a target for various attacks, including brute force attacks. If you don’t use or need XML-RPC, it’s often recommended to disable it.

Here’s how you can block access to xmlrpc.php using the modern Apache syntax:


<Files xmlrpc.php>
    Require all denied
</Files>

11 Prevent PHP Execution in Uploads Directory

Preventing the execution of PHP files in the uploads directory is a common security measure to prevent maliciously uploaded PHP scripts from executing. Here’s how you can block PHP execution within the WordPress uploads directory using modern Apache syntax:


<Directory /path/to/your/wordpress/wp-content/uploads/>
    <FilesMatch "\.php$">
        Require all denied
    </FilesMatch>
</Directory>

12 Disable Bad Actor From Embedding Your WordPress Site in iFrames

To prevent your WordPress site from being embedded in iframes on external sites, you can set the X-Frame-Options HTTP header to “SAMEORIGIN”. This will ensure that the site can only be embedded in an iframe on the same origin as the site itself.

You can set this header using the Apache configuration (typically .htaccess in the root of your WordPress installation) with the following code:

Here’s a breakdown of the directive:

  • Header set: This is used to set a specific HTTP header.
  • X-Frame-Options: This is the name of the HTTP header we’re setting.
  • “SAMEORIGIN”: This directive allows the site to be embedded within an iframe only if the iframe is on the same domain (origin) as the site itself.

Header set X-Frame-Options "SAMEORIGIN"

13 Set Content Security Policy

Description: Restrict sources from which content can be loaded to prevent cross-site scripting attacks.

To explain:

  • default-src ‘self’: By default, resources like scripts, images, and styles should only be loaded from the site’s own domain.
  • img-src ‘self’ https://images.trusted.com: Images can be loaded from the site’s domain and from https://images.trusted.com.
  • script-src ‘self’ https://scripts.trusted.com: JavaScript files can be loaded from the site’s domain and from https://scripts.trusted.com.
  • style-src ‘self’ https://styles.trusted.com: Stylesheets can be loaded from the site’s domain and from https://styles.trusted.com.
  • font-src ‘self’ https://fonts.trusted.com: Font files can be loaded from the site’s domain and from https://fonts.trusted.com.
  • object-src ‘none’: No embedded objects (like Flash or Java applets) are allowed.

# Setting Content Security Policy
Header set Content-Security-Policy "default-src 'self'; img-src 'self' https://images.trusted.com; script-src 'self' https://scripts.trusted.com; style-src 'self' https://styles.trusted.com; font-src 'self' https://fonts.trusted.com; object-src 'none';"

14 Set X-Content-Type-Options Header

The X-Content-Type-Options header is used to disable content type sniffing in browsers. When set to the value nosniff, it tells the browser to respect the Content-Type sent by the server and not try to detect the content type on its own. This can be useful to prevent some types of security vulnerabilities, such as MIME-type confusion attacks.

Here’s the code snippet to set the X-Content-Type-Options header in Apache’s .htaccess file:


# Set X-Content-Type-Options Header
Header set X-Content-Type-Options "nosniff"

15 Restrict PHP Execution in WP-Includes

The /wp-includes/directory is an essential part of a WordPress installation. It contains WordPress core files, and there shouldn’t be any reason for PHP execution directly from this directory from an external request, as it can be a security vulnerability.

To restrict PHP execution in the wp-includes directory, you can use the following configuration in your .htaccess file:


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^wp-includes/[^/]+\.php$ - [R=404,NC,L]
    RewriteRule ^wp-includes/js/tinymce/langs/.+\.php$ - [R=404,NC,L]
    RewriteRule ^wp-includes/theme-compat/ - [R=404,NC,L]
</IfModule>

16 Restrict Access to REST API

Certainly. The WordPress REST API provides developers with a way to interact with sites remotely by sending and receiving JSON objects. However, if you don’t need this functionality, it’s wise to restrict access to the REST API to enhance security and privacy.

Here’s how you can restrict access to the WordPress REST API using the Apache configuration (.htaccess file):


<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_URI} ^/wp-json/ [NC]
    RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.000
    RewriteRule .* - [F,L]
</IfModule>

If you’re not planning on allowing any specific IPs to access the REST API, simply remove or comment out the line containing  RewriteCond%{REMOTE_ADDR}.

Make sure to test the functionality of your site thoroughly after applying changes to ensure that you haven’t inadvertently disrupted any essential features. Also, keep in mind that some plugins or themes might rely on the REST API, so test them as well to confirm they still work as expected.

17 Disable Server Signature

To disable the server signature (i.e., to prevent Apache from broadcasting its version number and other details), you can use the ServerSignature directive in your .htaccess file.

The following is a  code snippet for disabling the server signature:


<IfModule mod_core.c>
    ServerSignature Off
</IfModule>

Adding this to your .htaccess will ensure that the server version details aren’t appended to server-generated pages, like error documents. This is a recommended security measure to make it slightly harder for potential attackers to identify and exploit known vulnerabilities in specific Apache versions.

18 Set HSTS Header

HTTP Strict Transport Security (HSTS) is a web security policy mechanism whereby a web server declares that complying user agents (like browsers) must only interact with it using secure HTTPS connections. This reduces the risk of man-in-the-middle attacks.

  1. Always Backup: Always back up your .htaccess file before making changes.
  2. Use With Care: Only add the preload directive if you are sure you want to submit your site to the HSTS preload list. Once a site is included in the preload list, it’s challenging to remove, and the site will always be forced to use HTTPS.
  3. Ensure SSL Certificate: Before implementing HSTS, ensure your website’s SSL certificate is correctly installed and that there are no mixed content issues.

<IfModule mod_headers.c>
    # Use HSTS and set its max-age to 1 year (31536000 seconds)
    # The 'includeSubDomains' directive is optional but recommended. It ensures all subdomains are HTTPS-only as well.
    # The 'preload' directive is optional. It signals that the domain is willing to be included in hard-coded lists that are shipped in browsers (be careful with this!).
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>

19 Prevent Username Enumeration

Username enumeration usually happens with WordPress when someone visits a URL like http://example.com/?author=1. The website can sometimes redirect to a URL with the author’s username, for instance: http://example.com/author/admin, revealing the username admin in this case.

To prevent this kind of username enumeration through the author archives in WordPress using .htaccess, you can use the following rules:


<IfModule mod_rewrite.c>
    RewriteEngine On
    # If the request contains an "author" parameter, redirect to the homepage
    RewriteCond %{QUERY_STRING} ^author= [NC]
    RewriteRule .* - [F,L]
</IfModule>

20 Deny Access to Debug Log

The WordPress debug log (debug.log) can sometimes contain sensitive information, so it’s a good idea to block public access to it.


<FilesMatch "debug\.log">
    Order allow,deny
    Deny from all
</FilesMatch>

21 Prevent Access to Plugin and Theme Editors

This  following code snippet  is used to disable the built-in theme and plugin editor in WordPress. This editor can be accessed from the WordPress dashboard and allows users with the right permissions to edit theme and plugin files directly from within the dashboard.

The reason you might want to disable this feature:

  1. Security: If a hacker gains access to your WordPress dashboard, they won’t be able to directly modify theme or plugin files to inject malicious code.
  2. Prevent Mistakes: It’s easy to break your site if you make a mistake while editing files via the dashboard. Disabling the file editor helps prevent these potential mistakes.
Where to put it:

You should place the define('DISALLOW_FILE_EDIT', true); line in your wp-config.php file. This file is located in the root directory of your WordPress installation. Simply add the line above anywhere in the file before the line that says /* That's all, stop editing! Happy publishing. */.

define('DISALLOW_FILE_EDIT', true);