“Hiding” WordPress installation files

By default, all the WordPress installation files are readable from a browser. What happens if example.com’s WordPress installation is at /wordpress/, and you go to http://example.com/wordpress/wp-content/plugins/ with your browser? Yes, you’re browsing the plugin directory. Obviously, changing permissions is not going to work, but it is possible to obscure the access to the files (hence “hiding”). Read on for details.

The WordPress default

Let us start by looking at a “standard” WordPress .htaccess file, to understand how requests are handled in a typical WordPress installation:

  1. <ifmodule>
  2. RewriteEngine On
  3. RewriteBase /
  4. RewriteCond %{REQUEST_FILENAME} !-f
  5. RewriteCond %{REQUEST_FILENAME} !-d
  6. RewriteRule . /index.php [L]</ifmodule>

This file contains a few directives for Apache. The <ifmodule> ... </ifmodule> tags tell Apache to care about their contents only if the specified module is installed. The remaining lines do the following:

  1. Start the mod_rewrite engine.
  2. Set the base for rewrite paths (the root of the web directory in this case). All paths will be interpreted as being below the path in this directive.
  3. If the file name the request maps to is not a file…
  4. …and it is not a directory…
  5. Then let it through to WordPress’ index.php for further handling.

This effectively means that as long as the request is not for a file or a directory below your web directory root, then WordPress handles it. If it is for a file or a directory, then Apache handles it (it falls through all the rewriting rules). This means that Apache will serve any file or directory as long as it can be read, which of course includes the WordPress installation files.

The solution

The desired situation is that people should only be able to read this files through “indirect” requests from your website, and not by accessing them directly. This can be achieved through a simple check of the HTTP Referer header :

  1. <ifmodule>
  2. RewriteEngine On
  3. RewriteBase /
  4. RewriteCond %{HTTP_REFERER} !^https?://(www.)?example.com/.* [NC]
  5. RewriteRule ^/?wordpress/?.* http://example.com/ [NC,L,R=301]
  6. RewriteCond %{REQUEST_FILENAME} !-f
  7. RewriteCond %{REQUEST_FILENAME} !-d
  8. RewriteRule . /index.php [NC,L]
  9. </ifmodule>

As you can see, two directives have been added to the default .htaccess file. They state the following:

  1. If the request did not come from a reference on the website, then…
  2. If the request was for a WordPress installation file, then redirect the request to the root of my website with a 301 response (moved permanently).

This rather simple and elegant solution denies direct access to the WordPress installation files from browsers.

Why does it say “hiding”?

Because people are still able to spoof the referer header to access your installation, meaning that it’s not an unbreakable solution.

Caveats

The drawback of this setup is mainly that denying direct access to the installation files means that you cannot access any of the files directly. This includes direct access to any images or other files you have uploaded, your administrator pages, etc.

To allow access to specific files and/or folders below your directory, add the following lines to your .htaccess file for each file or folder below the first RewriteCond line:

  1. RewriteCond %{REQUEST_URI} !^/some/directory/or/file

Note that this last example is case-sensitive, so be accurate. Also note that if the rule is for a folder, you should include /? at the end, to make sure the rule matches requests for both /some/folder and /some/folder/.

Another caveat is that this makes your blog inaccessible to people who don’t send referrer headers. If you think that people should be allowed not to do so, then don’t implement the solution in this post.

35 Comments so far »

  1. KeinEngel said

    on May 12 2006 at 23:58

    Wouldn’t it be easier if you’d just create a blank index.htm in all the folders? That would definetly get rid of the referer issue for wp-admin…

  2. Yongho Kim said

    on May 14 2006 at 22:31

    KeinEngel/ no, because they can still guess plugin file names and access them directly. Some plugin files can potentially contain passwords.

  3. deko said

    on May 24 2006 at 20:08

    Why bother hiding? Who cares if honest people can view your installation files?

    The people with evil intent are the ones who will spoof a referer. So if there are any vulnerabilities, hiding is not going to make you any less vulnerable.

    In any case…

    I’ve looked at the .htaccess file in my WP installation (version 2.0.2) and this is all there is:

    Options All -Indexes
    

    Is this something that is specific to the 2.0.2 version? Why don’t I have any rewrite rules?

    In regard to protecting installation files…

    If I try to go directly to:

    http://mysite.com/wordpress/wp-admin/options-general.php

    I get redirected to:

    http://mysite.com/wordpress/wp-login.php

    What causes this redirection?

    Why can’t this same mechanism be used to protect all installation files?

  4. Håvard said

    on May 25 2006 at 10:24

    If you do not wish to hide your installation files, you are of course free to ignore this article completely. :)

    The WordPress default I have pasted in the article is the default for installations with WordPress files in a directory of their own, hence the rewrite rules. They are not there if WordPress files and index.php are in the same directories.

    The redirection you mention is done by the function auth_redirect(), which checks if you are authenticated and redirects you to the login page if you are not.

  5. Jan said

    on July 10 2006 at 11:12

    Can you please tell me anything about my concern? here it is… i want to create a downloads page in my wordpress site, and i’v tried everything to reserve access only to people who are registered in my site. The only best best thing i’ve done so far is protect the downloads page using a login page but I can’t protect the files from being downloaded especially if the address is typed directly in the address bar! Can anyone please show me how to protect my files? Thanx in advance!

  6. Håvard said

    on July 11 2006 at 12:46

    Jan: This really isn’t a WordPress issue in my opinion, but what you can do to integrate the authentication for your files with that of WordPress is essentially one of two things: 1. Set up HTTP authentication for the location(s) of your file(s), and install the HTTP authentication for WordPress plugin. This will allow you to use HTTP authentication to access both WordPress and your files.
    2. Create a custom script that protects the files and call the WordPress authentication functions to authenticate users from it.

  7. Tom said

    on November 24 2007 at 22:05

    Hello How You did it that at your’s domain xo.no when i am trying to write http://ox.no/wp-admin there is no redirection to http://ox.no/wordpress/wp-admin ?

  8. Håvard said

    on November 25 2007 at 15:16

    Simply because there is no content on the site named wp-admin, and there is no such file or directory on the server either.

  9. Tom said

    on November 26 2007 at 02:45

    ehhh it was stupid question, i see now that i can move index.php to root directory and change path inside.

  10. Tom said

    on December 26 2007 at 19:06

    Might sound like a silly question, but after all this you told above, how will the owner access wordpress?

  11. Matt said

    on January 23 2008 at 19:24

    In the event that a pro tries to hack in by spoofing the http referer header, does it do any good to rename the wordpress installation file to something they wouldn’t guess?

  12. Matt said

    on January 23 2008 at 21:03

    I’m trying to insert the line that allows access to a specific file or directory but not having success. Could you add a sample of the example code with that line inserted? Maybe I’m not figuring the base or root directory correctly. Thank you.

  13. Håvard said

    on January 24 2008 at 00:30

    Matt: On your first question, renaming may hide things away slightly more, but it won’t keep professional people away.

    On your second question, I don’t understand what you are trying to achieve. The examples in the post show what you should need.

  14. Matt said

    on January 24 2008 at 16:18

    Thank you for the reply and the and the information about renaming.

    About the second question, the first part of the solution works for me when I insert the two lines of code and make that other change, changing [L] to [NC,L] in the last line.

    What I haven’t gotten to work yet is the line to access specific files or folders. I’m wondering if I am inserting it in the correct position, immediately after the first RewriteCond and before first RewriteRule, which are the two that were just added.

    Also I am wondering if I am specifying the path correctly.

    !^/some/directory/or/file

    Should it be:

    /home/whatever/public_html/wordpress/wp-login.php

    /wordpress/wp-login.php

    https?://(www.)?example.com/public_html/wordpress/wp-login-php

    or none of the above?

    Thanks again.

  15. Håvard said

    on January 24 2008 at 21:02

    Matt: To allow access to a specific file, the line should read:

    RewriteCond %{REQUEST_URI} !^/wordpress/wp-login.php

    This will allow direct access to the file wp-login.php in the wordpress directory below the root of your domain.

  16. Matt said

    on January 24 2008 at 23:39

    Thank you. That works great!

    I had to disable the AskApache Password Protect plugin. It installs an .aahtpasswd file and an .htaccess file on the web server to add another layer of protection to the wp-admin folder in the event that someone can get past the WordPress login page.

    I couldn’t get the two programs to work together. Do you know anything about that plugin or a similar layer of password protection?

    http://www.askapache.com/wordpress/htaccess-password-protect.html

    Do you recommend any other types of protection?

    Have you heard of the Login Lockdown plugin? http://www.bad-neighborhood.com/login-lockdown.html

  17. Håvard said

    on January 25 2008 at 00:31

    Matt: I am not familiar with any of those plugins, unfortunately.

    Generally, though, the amount of security measures you take (and the time you spend implementing them) should be proportional to the risks you’re facing.

    Try to assert why someone would want to break into your blog. Is it really necessary to put loads of hours into researching and implementing various security measures? When are you “secure”? What makes you convinced that these plugins themselves do not present new security holes?

    I am not saying that the steps you take are superfluous, but you should try and answer the questions above before you go ahead and implement a ton of presumably necessary security measures.

  18. Matt said

    on January 25 2008 at 00:53

    Maybe I should clarify. I can’t get the AskApache plugin to work with your strategy to hide the installation files. I have been checking out the HTTP Authentication plugin you mentioned. I would have to spend some time figuring it out since the documentation doesn’t cover all the details. Particularly the line: AuthUserFile /path/to/passwords is a kind of vague for a novice.

    It would be nice to get the AskApache plugin working since it’s a no-brainer. Any comments or suggestions? Is it worth it?

    Thanks again.

  19. Håvard said

    on January 25 2008 at 01:17

    Matt: The line should contain the full path (not URI) to your .aahtpasswd file. This really is a question for the plugin creator, though. :)

  20. Matt said

    on January 25 2008 at 17:07

    Thank you for that last answer about the path to .aahtpasswd. I really appreciate all your helpfulness. At least my installation files are “hidden” from the casual troublemakers and as I have time I can look into the http authentication later.

  21. Matt said

    on February 20 2008 at 23:15

    I really liked this scheme and had everything adapted to my site but ran into problem. I am using my WordPress blog to host a podcast and am using the podPress plugin to automate the generation of the podcast feed.

    My podcast media directory is named mp3 and is NOT located under wordpress/ but the podPress plugin writes the enclosure tag link to the file as such:

    http://mydomain.com/wordpress/podpress_trac/feed/10/0/podcast.mp3

    So the .htaccess rules keep iTunes and Juice from being able to access and download the file.

    podpress_trac is not a directory that shows up under the wordpress directory, maybe it is some kind of Permalink.

    Does anyone have any thought or suggestions about this? Thanks.

  22. Matt said

    on February 21 2008 at 06:04

    Okay, I guess I’ve got it figured out now with this line added:

    RewriteCond %{REQUESTURI} !^/wordpress/podpresstrac/feed/?

    That seems to work.

  23. Al said

    on March 8 2008 at 23:15

    I’m having trouble putting all the lines in the right order so I can gain access to wp-login, but redirect everything else in my wordpress folder.

    Can someone post the complete that includes the line (in the correct) place for wp-login?

    Or, do I need to put this line inside a .htaccess file within the wordpress folder?

    Thanks for your help.

    al

  24. Al said

    on March 9 2008 at 01:09

    Okay – I finally found the issue – I couldn’t get access to the login page without addressing both the wp-admin folder & login.php. In case other semi-novices are having troubles:

    My coding

    BEGIN WordPress

    RewriteEngine On RewriteBase / RewriteCond %{HTTPREFERER} !^https?://(www.)?example.com/.* [NC] RewriteCond %{REQUESTURI} !^/wordpress/wp-login.php RewriteCond %{REQUEST_URI} !^/wordpress/wp-admin/?

    RewriteRule ^/?wordpress/?.* http://Example.com/ [NC,L,R=301] RewriteCond %{REQUESTFILENAME} !-f RewriteCond %{REQUESTFILENAME} !-d RewriteRule . /index.php [NC,L]

    SecFilterRemove 114

    END WordPress

  25. AskApache said

    on April 19 2008 at 04:41

    Whoa.. this is actually not a good thing to do. I am assuming that your blog is at site.com/wordpress/ not site.com/

    This will 301 (permanently) redirect everyone who attempts to visit ANY page or post on your blog to site.com

    Anyone who clicks on a link to your blog from a google search result or any search engine result will be redirected to your site.com This will totally remove all posts and pages on your blog from ANY search engine index because the search engine robots crawl your site using blank referrers. This will block your Google Adsense from working because the Adsense robot uses a blank referrer.

    Anyone who would be attempting to hack your blog would be smart enough to instantly recognize that a referrer is needed and that is very easy to spoof. Most automated web scrapers and bots automatically spoof the referrer header so this doesn’t even block those.

    Another issue with this is that your code can be improved to:

    RewriteEngine On RewriteBase /

    RewriteCond %{HTTP_REFERER} !^http(s?)://([^.])(.?)example.com. [NC] RewriteRule ^/?wordpress/?(.*) http%1://%2%3example.com/ [NC,L,R=302]

    RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /wordpress/index.php [NC,L]

    One other issue I wondered about is what “installation files” you are talking about. The installation files are located in the wp-admin directory, so if you are going to use this method you should only be protecting the wp-admin directory.

    The AskApache Password Protect plugin approaches security through .htaccess in a very different way.

    The wp-admin directory is protected with http basic authentication password protection The wp-includes and wp-content folders are protected by disallowing any direct requests for files other than the static files like images, css, js, etc.

    Here’s the .htaccess used by the AskApache Password Protection plugin:

    RewriteEngine On RewriteBase / RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /wp-(includes|content)/.$ [NC] RewriteCond %{REQUEST_FILENAME} ^.+.php$ RewriteRule . – [F]

    I think I might just not be understanding this technique.. Enjoyed your article and discussion!

  26. Håvard said

    on April 19 2008 at 12:26

    AskApache: You’re not reading the post correctly. The wordpress installation referred to in the post is in a directory of it’s own, which eliminates the problem you first mention.

    The spoofing of referrer headers you mention is true, and the post also discusses this issue.

    The Wordpress installation files that the post refers to consists of all files installed when you install Wordpress, which includes both the wp-admin folder, and the other folders you mention.

    I fail to see how your supplied .htaccess file works. As far as I can tell, this will deny any request for files below wp-includes and wp-content which are not PHP files. Please correct me if I’m wrong.

    Your AskApache plugin solves a different problem, namely authenticating through HTTP Basic authentication, which itself is not a security measure, and relies on a secure connection, such as TLS, to achieve secure authentication (see Wikipedia for a basic introduction).

  27. AskApache said

    on April 21 2008 at 02:46

    HTTP Basic authentication, which itself is not a security measure, and relies on a secure connection, such as TLS, to achieve secure authentication

    Yes that is the most common argument against it, though its really a non-issue for 99% of sites. For an attacker to bypass the HTTP authentication on a non-encrypted connection, they would literally have to have a physical connection to a network path between an authenticated user and the server. The only issue is that the password and username are passed in cleartext, but you would have to be able to sniff the data off the wire to bypass it. As you can imagine, most of the automated exploit tools and bots performing mass exploits against WordPress blogs and other online software do not have this capability, so they are stopped dead. For sites that I admin that require a bit more security, I completely agree with you that SSL is mandatory, which encryts the connection and thereby prevents an attacker with physical access who can sniff your connection from seeing the password pass in plaintext. That can be a very secure setup.

    Your logic and solutions to this problem are completely accurate and insightful, as you say here:

    If it is for a file or a directory, then Apache handles it (it falls through all the rewriting rules). This means that Apache will serve any file or directory as long as it can be read, which of course includes the WordPress installation files…. The desired situation is that people should only be able to read this files through “indirect” requests from your website, and not by accessing them directly.

    The problem is accomplishing that by using the HTTP_REFERER header is simply impossible and ineffective. And of course the biggest problem for most blog admins would be the overwhelmingly negative effects on SEO.

    Apache’s modrewrite module only has 1 variable that can achieve your solution effectively, which is what the AskApache Password Protect plugin utilizes. Basically, the special variable THEREQUEST contains the FULL HTTP request sent by the client, in fact, the internall operation of the server rely almost exclusively on this variable to determine the values of other variables, like the QUERYSTRING, PROTOCOL, REQUESTMETHOD, etc.. THE_REQUEST looks like this.

    "GET /wordpress/wp-content/plugins/ HTTP/1.1"

    The reason this plugin is so special is because this is the only time the request is unfiltered. Basically ONLY external requests like clicking on a link or typing a link in a browser will cause this value. Other than using the REDIRECT_STATUS variable for those using cgi, this is the only way to determine whether a request is direct or indirect.

    To take it further, I’ve removed directory index generation and using the DirectoryIndex command to point to a static file we can make sure that directories can’t be browsed. I’ve also used the NS or nosubrequest flag to make sure internal uri requests by apache and other modules are able to pass through. I would encourage you to try accessing files at askapache.com if you would like a demonstration.

    Options -Indexes DirectoryIndex /wordpress/index.php

    ErrorDocument 403 /forbidden.html

    RewriteEngine On RewriteBase / RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /wordpress/wp-(includes|admin|content)/?.* [NC] RewriteCond %{REQUEST_FILENAME} ^.+.php$ [NC] RewriteRule .* – [F, NS]

    I just noticed you wrote this a couple years ago, which is before I heard of this technique. This is just to add to your excellent article, as its a great idea and one of the better and easier ways to really increase your security. Subscribed!

  28. AskApache said

    on April 21 2008 at 02:56

    WP-Plugin: AskApache Password Protect…

    AskApache Password Protect adds some serious password protection to your WordPress Blog’s admin directory. Imagine a HUGE brick wall protecting your frail .php scripts from the endless attacks of automated web robots and password-guessing exploi…

  29. Håvard said

    on April 21 2008 at 08:56

    <

    p>

    AskApache: Again, your claim that the “hiding” technique has negative effects on SEO are false. The article simply extends the Wordpress rewrite rules.

    Other than that, your reasoning about the article is mostly correct.

    Your reasoning about the security challenges with your own plugin, however, are faulty. If someone wants to bypass HTTP Basic authentication over an insecure transport, they will be able to do so. Without a secure transport, the authentication cannot be trusted.

  30. AskApache said

    on April 26 2008 at 18:03

    Havard,

    Ya interesting discussion. I would like for you to explain where I was incorrect in my assessment of the negative SEO aspect. Let me simplify my objections so you can better explain.

    Basically, Googlebot finds a link to Hiding wordpress installation files in the comments section of the AskApache Password Protect plugin page, so then Googlebot, which has to act in accordance with the RFC HTTP client specifications just like any user-agent/browser, makes a GET request for this page with the referral header being set as the askapache.com plugin page.

    If I understand, this code would then issue a 301 Permanent redirect to Googlebot instructing it to go to your blogs home page instead. When Googlebot or other search engine robots receive a 301 permanent redirect they usually take the redirecting URL OUT of their search index.

    But once googlebot was on your homepage, any links to your site that it followed from there WOULD be allowed because googlebot would then set the referall header to your site.

    When Googlebot receives a 301 redirect like this, it actually transfers some of the page-rank associated with the page issusing the redirect, to the page it is being redirected to. So this could actually boost the page-rank of your home-page, its a tricky thing.

  31. Håvard said

    on April 27 2008 at 14:08

    AskApache: What the article does is to add a few lines before the default Wordpress rewrite rules.

    The added lines state that if the request is for a file in the wordpress directory (which can/must be changed if the directory does not match your installation), and the referer is not the site (which can/must also be changed to match your installation), then the request is redirected to the root of the site (which can/must also be changed to match your preference and installation).

    The remaining ruleset is the Wordpress default, which is the same in any Wordpress installation which manages .htaccess. Reading the ruleset, you can see that it lets each request through for handling by index.php, unless the request is for an actual directory or file. I suggest you read the Wordpress documentation on permalinks and read the parts of Wordpress that handle permalinks (i.e. index.php and referred functions) for a detailed explanation.

    If you have issues with the default ruleset, I suggest you contact the Wordpress development team.

  32. AskApache said

    on April 28 2008 at 00:18

    Edit: Removed yet another ad from this guy -H

    I’m still not seeing what this actually does. So you are saying that these rules should only be put in place BEFORE you actually install wordpress?

  33. Håvard said

    on April 28 2008 at 00:24

    No. The article describes a technique for “hiding” your Wordpress installation from public access, which obviously requires a working Wordpress installation.

  34. AskApache said

    on April 29 2008 at 05:51

    OH! Well then that makes perfect sense, I was reading too much into it.

    Sorry about posting a link to [ad removed] in response to your suggestion I contact WP support for “help”. Sorry for spamming your thread bro.

    ~Out

  35. WordPress Installation Techniques | Guide for Blogs and Wordpress said

    on October 13 2008 at 07:02

    [...] “Hiding” WordPress Installation Files (offsite) [...]

Comment RSS · TrackBack URI

Leave a comment

Name: (Required)

eMail: (Required)

Website:

Comment:

Get Adobe Flash playerPlugin by wpburn.com wordpress themes