Many Wordpress plugins allow themselves to be accessed directly from the internet, when they:

  1. don’t need it, and
  2. aren’t built for it.

They don’t even try to block the direct access, and it can compromise the site’s security.

What I’m referring to is when an attacker will try to access the plugin’s PHP files directly. You may have seen something like this in your logs:

... "GET /wp-content/plugins/SomePlugin/files.php" 404 ...
... "POST /wp-content/plugins/AnotherPlugin/init.php" 200 ...
... "GET /wp-content/plugins/HiPlugin/start.php" 404 ...

In my website logs, I see lots and lots of attempts such as these were bots are looking to see what plugins are installed, and testing for vulnerabilities.

And if a vulnerable plugin exist (those are just made up names in the above example), then the attacker may be able to get into your site. (In the example, the second one exists and just got hit with the attacker’s payload).

And the saddest part: it’s so easy to stop.

So Easy, Yet so Forgotten

A developer can add the following to help stop these direct attacks on their plugin:

<?php if ( !defined('ABSPATH') ) {
  die("<!-- Not allowed -->"); 
// start the program here

Of course there are other ways to do it, and perhaps even better ways. But the above code gives you the idea.

Some may say “Why? My code probably can’t do anything, and by default it does nothing.”

The reason is because code does not always act the way we want it to. It doesn’t know good from bad. It doesn’t know our desires. All it knows is how to do what it is “suppose to do”. And that may not be what we want it to.

It’s better to be safe than sorry.

There are plugins that will block people who trigger too many “File not found” errors (404). But sometimes this hurts your users. And bots can come from all sorts of different ip addresses, even when used by the same person.

And all they need in one php file that will do their bidding.

In light of these issues, and because so many plugins don’t block direct access to themselves, if your hosting provider allows .htaccess, you may want to try the following code. Just add it to the top (or middle) of the htaccess file, before the Wordpress part if you’re using permalinks.

The Code

# block access to plugins and theme files 
# Selects themes, plugins, but below we can allow some files before we actually block 
# Will block php, txt, json, and anything else that is not allowed below 

RewriteCond %{REQUEST_URI}  ^.*/wp-content/(plugins|themes)/.*  [NC,OR] 

# Block anything in wp-content that ends in php (remove above OR if you dont use this) 
RewriteCond %{REQUEST_URI}  ^.*/wp-content/.*\.php$  [NC] 

# Allow these file types 
RewriteCond %{REQUEST_URI}  !.*\.(js|css|woff|jpe?g|gif|png|bmp|svg|swf|ttf|eot|otf)$ [NC] 

# Allow these files to be directly accessed. Even if they are php or whatever. 
#RewriteCond %{REQUEST_URI}  !^.*/wp-content/plugins/needs-direct-access/needs-direct-access\.php$  [NC] 
#RewriteCond %{REQUEST_URI}  !^.*/wp-content/plugins/needs-direct-access/needs-direct-access\.json$  [NC] 

# Allow access to db-error.php. if you are using it 
RewriteCond %{REQUEST_URI}  !^.*/wp-content/db-error\.php$  [NC] 

# Decide how you will block it. Note how we block regardless 
# of whether the file actually exists or not 
RewriteRule ^(.*)$ - [F] 
#RewriteRule ^(.*)$ - [R=404,L] 
#RewriteRule ^(.*)$ - [R=410,L]

Here’s how it works. in the first part we only select the themes and plugins folders, and anything in wp-content or above (below?) ending in php. Just to be safe.

The second section is a list of file types that plugins make available, such as pictures, javascript, and fonts, which need to be served directly to the user.

Then we have a commented out (disabled) line that will exclude (allow) certain plugin files. Some plugins need and are designed for direct access. What files do you put in there? Pretty much anything that gets blocked. You’ll know it when you try to save some settings in a plugin or try to go to a page on your website and notice a “blocked” error message in your network console/log. (Or whatever you have for a 403, 404, 410… see the next part).

I also put in db-error.php in case you are using that to show a nice page if your database ever goes down… which is better than “Wordpress had a problem” (and the user is saying “word-what?”).

Okay, now the last section is where the blocking happens. And here you have a choice. You can block all of them, using the [F] which will send a 403 “access denied”.

Or just say “Oh, not here” [R=404,L]. The “oh not here (404)” is nice if you are using awstats or a 404 logger, so you can easily keep track of what may be causing users problems. (Most log display programs don’t show 403 ([F]) errors.)

Or you can do a 410 “Page move without a forwarding address.” That’s nice if you get a lot of 403 and 404’s and you want to know the difference.

Final Thoughts

As a final note, realize that this method will not hide which plugins you are using. It still allows access to the javascript and css files, so an attacker can still scan for those and find out which plugins you are using.

Best advice: use only the plugins you need, update the ones you have, and make sure you can trust the plugin authors.