Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save asciitohex/ab89ec9c0b0145f5527ebfee336defcd to your computer and use it in GitHub Desktop.
Save asciitohex/ab89ec9c0b0145f5527ebfee336defcd to your computer and use it in GitHub Desktop.
WordPress Hardening

Hardening WordPress

Securing WordPress using a combination of configuration changes and plugins.

.htaccess and wp-config.php tasks

1. Add keys to wp-config.php

2. Hide .htaccess and wp-config.php

<Files .htaccess wp-config.php>
order allow,deny
deny from all
</Files>

3. Move wp-config.php to another location and create a new wp-config.php to include it

<?php
define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . '../path/to/wp-config.php');

4. Disable file editing. Add the following to wp-config.php

define('DISALLOW_FILE_EDIT', true);

5. Disable access to wp-includes/

# Block wp-includes folder and files
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
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]
</IfModule>

6. Prevent username enumeration

RewriteCond %{QUERY_STRING} author=d
RewriteRule ^ /? [L,R=301]

7. Prevent script injection

Options +FollowSymLinks
RewriteEngine On
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]

8. Prevent PHP execution using .htaccess. This .htaccess files goes in wp-content/uploads/.

# Kill PHP Execution
<Files *.php>
deny from all
</Files>

9. Disable xml-rpc.php if not using mobile app for site management

<files xmlrpc.php>
order allow,deny
deny from all
</files>

10. Limit Login to Specific IP

<IfModule mod_rewrite.c>
	RewriteEngine on
	RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
	RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$ [OR]
	RewriteCond %{REQUEST_URI} ^(.*)?wp-admin/$
	RewriteCond %{REMOTE_ADDR} !^63\.224\.182\.124$
	RewriteCond %{REMOTE_ADDR} !^96\.81\.205\.229$
	RewriteRule ^(.*)$ - [R=403,L]
</IfModule>

Plugins and Other

1. Install Exploit Scanner

2. Limit login attempts via functionality plugin or functions.php theme file

<?php
/**
 * CLASS LIMIT LOGIN ATTEMPTS
 * Prevent Mass WordPress Login Attacks by setting locking the system when login fail.
 * To be added in functions.php or as an external file.
 */
if ( ! class_exists( 'Limit_Login_Attempts' ) ) {
    class Limit_Login_Attempts {

        var $transient_name     = 'attempted_login';

        public function __construct($attempts = 3, $duration = 1800) {
            $this->failed_login_limit = $attempts;
            $this->lockout_duration = $duration; 
            add_filter( 'authenticate', array( $this, 'check_attempted_login' ), 30, 3 );
            add_action( 'wp_login_failed', array( $this, 'login_failed' ), 10, 1 );
        }

        /**
         * Lock login attempts of failed login limit is reached
         */
        public function check_attempted_login( $user, $username, $password ) {
            if ( get_transient( $this->transient_name ) ) {
                $datas = get_transient( $this->transient_name );
                
                if ( $datas['tried'] >= $this->failed_login_limit ) {
                    $until = get_option( '_transient_timeout_' . $this->transient_name );
                    $time = $this->when( $until );

                    return new WP_Error( 'too_many_attempts', sprintf( __( '<strong>ERROR</strong>: You have reached authentification limit, you will be able to try again in %1$s.' ) , $time ) );
                }
            }

            return $user;
        }


        /**
         * Add transient
         */
        public function login_failed( $username ) {
            if ( get_transient( $this->transient_name ) ) {
                $datas = get_transient( $this->transient_name );
                $datas['tried']++;

                if ( $datas['tried'] <= $this->failed_login_limit )
                    set_transient( $this->transient_name, $datas , $this->lockout_duration );
            } else {
                $datas = array(
                    'tried'     => 1
                );
                set_transient( $this->transient_name, $datas , $this->lockout_duration );
            }
        }


        /**
         * Return difference between 2 given dates
         * @param  int      $time   Date as Unix timestamp
         * @return string           Return string
         */
        private function when( $time ) {
            if ( ! $time )
                return;

            $right_now = time();

            $diff = abs( $right_now - $time );

            $second = 1;
            $minute = $second * 60;
            $hour = $minute * 60;
            $day = $hour * 24;

            if ( $diff < $minute )
                return floor( $diff / $second ) . ' seconds';

            if ( $diff < $minute * 2 )
                return "about 1 minute ago";

            if ( $diff < $hour )
                return floor( $diff / $minute ) . ' minutes';

            if ( $diff < $hour * 2 )
                return 'about 1 hour';

            return floor( $diff / $hour ) . ' hours';
        }
    }
}

// enable login attempt limiting
new Limit_Login_Attempts(3,1800);

3. If you need functionality not already provided above, like blocking known attackers, install WordFence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment