2.14.1.17. Hacking protection for WordPress sites

Attention!

Before performing any actions, create backups of your site and database.
  1. Regularly update WordPress, themes, and plugins. Currently, WordPress CMS accounts for over 30% of all sites worldwide. Due to its widespread use, WordPress is a popular target for hackers. Since WordPress is an open-source CMS, anyone can access the code to learn and improve it. However, this also means that hackers can study it and find ways to infiltrate sites.
    Whenever a vulnerability is discovered, WordPress developers make every effort to release an update that fixes the problem. If you are not using the latest version of WordPress, you are using software with known security vulnerabilities.
  2. Delete all unused themes from the wp-content/themes directory and unused plugins from the wp-content/plugins directory. Storing free themes and plugins significantly increases the likelihood of your site being hacked. Even if a theme or plugin is inactive, it still allows malicious scripts to be opened from the wp-content/themes/theme_name or wp-content/plugins/plugin_name directory.
  3. More than half of free WordPress themes are infected or vulnerable. If you download premium themes from a resource where they are available for free, there is a high probability that they contain malicious code or spam links.
  4. Disable the display of the WordPress version. Malicious users will not be able to identify the version, and therefore will not be aware of any vulnerabilities. To do this, open the file wp-content/themes/theme_name/functions.php and add the following code after the first line:
    function remove_wordpress_version_number() {
        return '';
    }
    add_filter('the_generator', 'remove_wordpress_version_number');
    function remove_version_from_scripts( $src ) {
       if ( strpos( $src, '?ver=' ) )
           $src = remove_query_arg( 'ver', $src );
       return $src;
    }
    add_filter( 'style_loader_src', 'remove_version_from_scripts');
    add_filter( 'script_loader_src', 'remove_version_from_scripts');
  5. Move the configuration file wp-config.php to the directory above the current one. The wp-config.php file contains the basic settings for your site and is the most important file in the root directory of the site. In the current WordPress architecture, the configuration file is checked with the highest priority. Thus, even if it is stored in a folder above the root directory, WordPress will still be able to see it. You need to move this file from the /home/hosting_account_name/site_name/www/ directory to the /home/hosting_account_name/site_name/ directory. Warning! This recommendation only applies if you are using a single subdomain.
  6. Change the database prefix. You can do this using phpMyAdmin:
    1. Execute SQL query SHOW TABLES:
    2. Copy the resulting list of all tables in the database and create a similar query for each table:
      RENAME TABLE `wp_comments` TO `wpnew1_comments`;
    3. Execute the created queries:
    4. To ensure that the prefix wp_ is no longer in use, execute the following query:
      SELECT * FROM `wpnew1_options` WHERE `option_name` LIKE '%wp_%'
    5. Manually change the prefix to a new one using the "Change" button:
    6. Perform the same actions after executing the following query:
      SELECT * FROM `wpnew1_usermeta` WHERE `meta_key` LIKE '%wp_%'
    7. As a final step, open the configuration file wp-config.php and change the prefix to the new one:
  7. Change the default administrator login admin:
    1. Log in to phpMyAdmin, select the database, and find the table prefix_users:
    2. Open it and find the login admin in the user_login column. Click "Change", enter the new administrator login, and save the changes:
    3. Change the URL of the admin panel. To do this, use the Protect Your Admin plugin:
      1. Install the plugin, open the plugin menu, replace the URL, and click "Save":
      2. In the "Settings → Permalinks" section, set the "Post name" option:The admin panel will now be accessible at example.com/wpmyadmin.
  8. Deny access to the xmlrpc.php file. To do this, add one of the following restrictions to the .htaccess file in the root directory of the site:
    # Redirect to local address. The log will contain response code 301..
    RewriteRule ^xmlrpc\.php$ "http\:\/\/127\.0\.0\.1\/" [R=301,L]
    
    # We deny access to the file. The log will contain response code 403.
    RewriteRule ^xmlrpc.php$ - [F,L]
  9. Disable the execution of PHP files in the uploads directory. This is a common place for viruses to be uploaded. To prevent malicious code from being executed in the event of a hack, add the following code to the wp-content/uploads/.htaccess file:
    <Files ~ "\.(php)$">
      Order allow,deny
      Deny from all
    </Files>
  10. Restrict access to the admin panel by IP. This recommendation is suitable for those who have a static IP address. Add the following code to the .htaccess file in the root directory of the site:
    RewriteEngine on
    RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$ [OR]
    RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$
    RewriteCond %{REMOTE_ADDR} !^127.0.0.1$
    RewriteRule ^(.*)$ - [R=403,L]

    Replace 127.0.0.1 with your IP address.

  11. A small firewall for the site. These rules may slow down the site, as each request to the site will be analyzed using them. If you notice a significant decrease in the speed of the site, you should not use these rules. Instead, try using the Wordfence Security — Firewall & Malware Scan plugin.
    Add the firewall code to the .htaccess file in the root directory of the site:
    <IfModule mod_rewrite.c>
    RewriteEngine On
    # XSS blocking
    RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
    # Blocking the setting of PHP GLOBALS variables via URL
    RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
    # Block the ability to change the _REQUEST variable via URL
    RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
    # Blocking MySQL injections, RFI, base64, etc.
    RewriteCond %{QUERY_STRING} (javascript:)(.*)(;) [NC,OR]
    RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=http:// [OR]
    RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=(\.\.//?)+ [OR]
    RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=/([a-z0-9_.]//?)+ [NC,OR]
    RewriteCond %{QUERY_STRING} \=PHP[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} [NC,OR]
    RewriteCond %{QUERY_STRING} (\.\./|\.\.) [OR]
    RewriteCond %{QUERY_STRING} ftp\: [NC,OR]
    RewriteCond %{QUERY_STRING} http\: [NC,OR]
    RewriteCond %{QUERY_STRING} https\: [NC,OR]
    RewriteCond %{QUERY_STRING} \=\|w\| [NC,OR]
    RewriteCond %{QUERY_STRING} ^(.*)/self/(.*)$ [NC,OR]
    RewriteCond %{QUERY_STRING} ^(.*)cPath=http://(.*)$ [NC,OR]
    RewriteCond %{QUERY_STRING} (\<|%3C).*script.*(\>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} (<|%3C)([^s]*s)+cript.*(>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} (\<|%3C).*iframe.*(\>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} (<|%3C)([^i]*i)+frame.*(>|%3E) [NC,OR]
    RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|%3D) [OR]
    RewriteCond %{QUERY_STRING} base64_encode.*\(.*\) [NC,OR]
    RewriteCond %{QUERY_STRING} base64_(en|de)code[^(]*\([^)]*\) [NC,OR]
    RewriteCond %{QUERY_STRING} ^.*(\[|\]|\(|\)|<|>).* [NC,OR]
    RewriteCond %{QUERY_STRING} (NULL|OUTFILE|LOAD_FILE) [OR]
    RewriteCond %{QUERY_STRING} (\./|\../|\.../)+(motd|etc|bin) [NC,OR]
    RewriteCond %{QUERY_STRING} (localhost|loopback|127\.0\.0\.1) [NC,OR]
    RewriteCond %{QUERY_STRING} (<|>|'|%0A|%0D|%27|%3C|%3E|%00) [NC,OR]
    RewriteCond %{QUERY_STRING} concat[^\(]*\( [NC,OR]
    RewriteCond %{QUERY_STRING} union([^s]*s)+elect [NC,OR]
    RewriteCond %{QUERY_STRING} union([^a]*a)+ll([^s]*s)+elect [NC,OR]
    RewriteCond %{QUERY_STRING} (;|<|>|'|"|\)|%0A|%0D|%22|%27|%3C|%3E|%00).*(/\*|union|select|insert|drop|delete|update|cast|create|char|convert|alter|declare|order|script|set|md5|benchmark|encode) [NC,OR]
    RewriteCond %{QUERY_STRING} (\\|\.\.\.|\.\./|~|`|<|>|\|) [NC,OR]
    RewriteCond %{QUERY_STRING} (boot\.ini|etc/passwd|self/environ) [NC,OR]
    RewriteCond %{QUERY_STRING} (thumbs?(_editor|open)?|tim(thumb)?)\.php [NC,OR]
    RewriteCond %{QUERY_STRING} (sp_executesql) [NC]
    RewriteCond %{QUERY_STRING} (eval\() [NC,OR]
    RewriteCond %{QUERY_STRING} ([a-z0-9]{2000,}) [NC,OR]
    RewriteRule ^(.*)$ - [F,L]
    # Blocking known Shells
    RewriteEngine on
    RewriteCond %{REQUEST_URI} .*((php|my)?shell|remview.*|phpremoteview.*|sshphp.*|pcom|nstview.*|c99|r57|webadmin.*|phpget.*|phpwriter.*|fileditor.*|locus7.*|storm7.*).(p?s?x?htm?l?|txt|aspx?|cfml?|cgi|pl|php[3-9]{0,1}|jsp?|sql|xml) [NC,OR]
    RewriteCond %{REQUEST_METHOD} (GET|POST) [NC]
    RewriteCond %{QUERY_STRING} ^(.*)=(/|%2F)(h|%68|%48)(o|%6F|%4F)(m|%6D|%4D)(e|%65|%45)(.+)?(/|%2F)(.*)(/|%2F)(.*)$ [OR]
    RewriteCond %{QUERY_STRING} ^work_dir=.*$ [OR]
    RewriteCond %{QUERY_STRING} ^command=.*&output.*$ [OR]
    RewriteCond %{QUERY_STRING} ^nts_[a-z0-9_]{0,10}=.*$ [OR]
    RewriteCond %{QUERY_STRING} ^c=(t|setup|codes)$ [OR]
    RewriteCond %{QUERY_STRING} ^act=((about|cmd|selfremove|chbd|trojan|backc|massbrowsersploit|exploits|grablogins|upload.*)|((chmod|f)&f=.*))$ [OR]
    RewriteCond %{QUERY_STRING} ^act=(ls|search|fsbuff|encoder|tools|processes|ftpquickbrute|security|sql|eval|update|feedback|cmd|gofile|mkfile)&d=.*$ [OR]
    RewriteCond %{QUERY_STRING} ^&?c=(l?v?i?&d=|v&fnot=|setup&ref=|l&r=|d&d=|tree&d|t&d=|e&d=|i&d=|codes|md5crack).*$ [OR]
    RewriteCond %{QUERY_STRING} ^(.*)([-_a-z]{1,15})=(chmod|chdir|mkdir|rmdir|clear|whoami|uname|unzip|gzip|gunzip|grep|more|umask|telnet|ssh|ftp|head|tail|which|mkmode|touch|logname|edit_file|search_text|find_text|php_eval|download_file|ftp_file_down|ftp_file_up|ftp_brute|mail_file|mysql|mysql_dump|db_query)([^a-zA-Z0-9].+)*$ [OR]
    RewriteCond %{QUERY_STRING} ^(.*)(wget|shell_exec|passthru|system|exec|popen|proc_open)(.*)$
    RewriteRule .* - [F]
    </IfModule>
Content

    (6)