How to secure WordPress admin directory on IIS 7.0

Recently I was told about Smashing Magazine, which turned out to be a pretty useful site. It is targeted for web developers and web designers and it contains tons of information, tools and freebies for web developers. One of the article on that site was about 10 Steps To Protect The Admin Area in WordPress. In that article step #7 described how to use web server’s built-in authentication to provide an extra protection layer for wp-admin directory, where all WordPress admin scripts are located. The article described how to do that in Apache by using .htaccess file. In this post I will explain how to protect WordPress wp-admin directory on IIS 7.0 by using IIS built-in Forms Authentication.

Prerequisites

First thing to make sure is to confirm that IIS Forms authentication and URL authorization modules are installed and enabled on your IIS server. To quickly check that, open an elevated command prompt and run the following commands:

C:\Windows\System32\inetsrv>appcmd list modules | find "FormsAuthentication"
MODULE "FormsAuthentication" ( type: System.Web.Security.FormsAuthenticationModule,preCondition: )

C:\Windows\System32\inetsrv>appcmd list modules | find "UrlAuthorization"
MODULE "UrlAuthorization" ( type: System.Web.Security.UrlAuthorizationModule, preCondition: managedHandler )
MODULE "UrlAuthorizationModule" ( native, preCondition: )

If the output from the commands look similar to above then the necessary modules are installed.

Another thing to ensure is that the WordPress site is hosted in an Application Pool with Integrated Managed Pipeline Mode. To check that run the following command from elevated command prompt (replace “DefaultAppPool” with the name of your AppPool if necessary):

C:\Windows\System32\inetsrv>appcmd list apppools | find "DefaultAppPool"
APPPOOL "DefaultAppPool" (MgdVersion: v2.0,MgdMode: Integrated,state: Started)

The output should contain MgdMode:Integrated.

Configuration steps

Download the zip file below:

IIS Forms Authentication for WordPress (4.1 KB)

Extract the two folders – App_Code and App_Data – into the root folder or your WordPress web site. For example if your WordPress files are located in folder C:\inetpub\wwwroot, then there should be two new files present at the following paths:

C:\inetpub\wwwroot\App_Code\CustomProvider.cs
C:\inetpub\wwwroot\App_Data\Users.xml

The CustomProvider.cs file contains an implementation of membership provider for IIS that uses Users.xml file as a storage for usernames and passwords. To learn more about providers refer to the Membership Providers article. The code for CustomProvider.cs was literally copied from that article.

The Users.xml file is a simple credentials storage. Its content looks like below:

<Users>
  <User>
    <UserName>your_username_here</UserName>
    <Password>your_password_here</Password>
    <EMail>user@ruslany.net</EMail>
  </User>
</Users>

Note that it is important that this file is located in App_Data directory, because this directory, as well as App_Code, is protected by IIS Request Filtering module, which prevents anyone from requesting any files from these folders.

Also it is important to pick a user name and a password different from the ones that are used for the WordPress authentication.

Also extract the Login.aspx file into the root folder of the web site, e.g.:

C:\inetpub\wwwroot\Login.aspx

Now you need to register this custom membership provider and enable Forms authentication. To do that add the following XML configuration element into the <configuration> element inside of the web.config file, located at the root folder of your WordPress site:

<system.web>
  <authentication mode="Forms" />
  <membership defaultProvider="AspNetReadOnlyXmlMembershipProvider">
    <providers>
       <add name="AspNetReadOnlyXmlMembershipProvider"
                type="ReadOnlyXmlMembershipProvider"
                description="Read-only XML membership provider"
                xmlFileName="~/App_Data/Users.xml" />
    </providers>
  </membership>
</system.web>

With the custom provider registered and configured, it is time to configure IIS to protect wp-admin folder with extra authentication that uses this custom provider. To do that add the following XML configuration fragment into the <configuration> element inside of the web.config file, located at the root folder of the WordPress site:

<!-- Deny access to wp-admin for anonymous users -->
<location path="wp-admin">
  <system.webServer>
      <security>
          <authorization>
              <add accessType="Deny" users="?" />
          </authorization>
      </security>
  </system.webServer>
</location>
<!-- Allow access to wp-admin/css folder for anonymous users -->
<!-- this is needed in order for WordPress login page to display correctly -->
<location path="wp-admin/css">
  <system.webServer>
    <security>
      <authorization>
        <remove users="?" roles="" verbs="" />
      </authorization>
    </security>
  </system.webServer>
</location>
<!-- Allow access to wp-admin/images folder for anonymous users -->
<!-- this is needed in order for WordPress login page to display correctly -->
<location path="wp-admin/images">
  <system.webServer>
    <security>
      <authorization>
        <remove users="?" roles="" verbs="" />
      </authorization>
    </security>
  </system.webServer>
</location>

Last thing you will need to do is to configure IIS modules that are used for Forms Authentication and for URL authorization to run for all kinds of requests. Both those modules are Managed modules (that is they are implemented using .NET Framework) and by default they are configured to run only for requests that are made for ASP.NET content. In order for them to work with PHP content then need to be configured to run for all requests. This can be done by using the following configuration section. Add it inside of <configuration>/<system.webServer> element:

<modules>
    <remove name="UrlAuthorization" />
    <remove name="FormsAuthentication" />
    <remove name="DefaultAuthentication" />
    <add name="DefaultAuthentication"
               type="System.Web.Security.DefaultAuthenticationModule" preCondition="" />
    <add name="FormsAuthentication"
               type="System.Web.Security.FormsAuthenticationModule" preCondition="" />
    <add name="UrlAuthorization"
               type="System.Web.Security.UrlAuthorizationModule" preCondition="" />
</modules>

At this point the WordPress wp-admin directory is protected by IIS Forms Authentication in addition to the standard WordPress authentication. Update the users.xml file with the username and password of your choice and then try to request http://localhost/wp-admin/. The IIS Forms Authentication logon page will be shown first:

FormsAuthPage

Once you provided the user name and password as specified in users.xml file, the standard WordPress login page will be shown next. In that page you can provide your WordPress logon credentials:

WordPressAuth

Now the entire content of the wp-admin directory is protected by two different authentication mechanisms, which makes it harder for anyone to hack into it. And the nice thing about this configuration is that it can be done even if your site is hosted on a shared server. All that is necessary is to have ASP.NET and IIS URL authorization enabled on a shared server by a shared hosting provider.

Download all the necessary files as well as an example of web.config file from the link below.

IIS Forms Authentication for WordPress (4.1 KB)

Also check out how this protection actually works by trying to navigate to http://ruslany.net/wp-admin/.

4,467 views

ruslany on February 6th 2009 in PHP, WordPress

PoorFairAverageGoodExcellent (3 votes, average: 5.00 out of 5)

9 Responses to “How to secure WordPress admin directory on IIS 7.0”

  1. Gravatar Imageruslany responded on 18 Feb 2009 at 7:17 pm #

    Update: if you use URL rewriter to enable pretty permalinks in WordPress, then it may cause some javascript errors on login.aspx page. To prevent those errors, modify the rewrite rule for WordPress permalinks as below:

    <rule name="WordPress" patternSyntax="Wildcard">
    <match url="*" />
    <conditions>
    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
    <add input="{URL}" negate="true" pattern="*.axd" />
    </conditions>
    <action type="Rewrite" url="index.php" />
    </rule>

  2. Gravatar ImageDoYouKnow.IN responded on 15 Sep 2009 at 3:51 am #

    How to secure non-Admin Directory?

  3. Gravatar ImageChris responded on 17 Oct 2009 at 3:07 pm #

    Interesting idea but I have some issues/questions:

    Why wouldn’t you use the built-in Basic Authentication for this? Using the IIS GUI you can configure the wp-admin folder for basic authentication and leverage the built in Windows accounts instead of keeping them in a file on your computer.

    You don’t mention anything about encrypting the user xml file. This should be done to prevent accidential exposure from a number of possibilities.

    Also, you don’t mention anything about using SSL, having not one, but two forms based authentication interchanges over http instead of https. In IIS 7.0/7.5 you can use the built-in URL Rewriter to switch all calls to the wp-admin and wp-login path over to https…

    You also don’t mention anything about using different account/passwords for WordPress and for your FBA… I would guess that many users would simply use the same credentials for both, making it pointless to have multiple credential challenges.

    Over all it’s an interesting idea but I think you might want to spend some more time polishing off the recommendation.

  4. Gravatar Imageruslany responded on 17 Oct 2009 at 4:07 pm #

    @Chris: Thanks for your comment – these are very valid questions/issues. Let me try to answer them one by one:

    1. The reason I choose not to use built-in IIS basic authentication is because it relies on the built-in Windows accounts, which makes this option not feasible in a shared hosting environment. Plus with basic authentication the base64 encoded password is sent on every request, while with FBA the password is sent on a login request only, while all the other requests use session cookie. So basic auth may be actually less secure because the password is exposed on the wire for longer time.
    2. You could encrypt the xml file with the password if you want to, but since it is located in App_Data folder it is already protected by IIS Request Filtering module and hence will not be exposed to the site visitors.
    3. Using SSL is the best option of course, but again it is not always possible in a shared hosting environment. If you used SSL then it would not even be necessary to use this extra protection with FBA.
    4. Yes, it is very recommended to use different user name and password for the WordPress and FBA. In fact that’s what I use on my site. I will update the article to make it clear.

    Overall, this kind of protection is not super strong, but it is better than nothing, especially if somebody uses shared hosting environment for their WordPress blog.

  5. Gravatar Imageavn responded on 30 Nov 2009 at 12:02 pm #

    Hi,
    nice and useful.
    Now I followed your instructions and get a secure wp-admin. I have a issue. If I tried to upload a file through new-media.php in flash mode it asking me a login in a window after showing progress on file upload. when giving credentials give me the famous webconfig uncustomized error. In normal mode is working fine.also direct from server is working fine in both modes.
    I suppose that this is due to flash uploader not sending the cookie to server.
    Any ideea?

  6. Gravatar Imageavn responded on 30 Nov 2009 at 12:47 pm #

    update
    this behavior is only in Firefox and not in IE (this is why on the server is working fine as I used the IE) it seems it is a bug/feature:) in how the flash in browser treat cookies needed by IIS.
    Anyway the issue still remain…

  7. Gravatar ImageJohn responded on 08 Dec 2009 at 9:43 am #

    Hello,
    I am confused about the location element. For some reason, the following does not work for me (I am using this technique to secure a PhpMyAdmin site):
    <location path="3_2_4">
    <system.webServer>
    <security>
    <authorization>
    <add accessType="Deny" users="?" />
    </authorization>
    </security>
    </system.webServer>
    </location>

    But the following does work:
    <location path="3_2_4">
    <system.web>
    <authorization>
    <deny users="?" />
    </authorization>
    </system.web>
    </location>

    I thought that in integrated pipeline mode, that the system.webServer namespace was the one that was in charge of securing all files (php,jpg etc), but in my case only system.web works. Can you clarify please? Sorry for the double post, I forgot to encode the first one. Much appreciated.
    John

  8. Gravatar Imageruslany responded on 08 Dec 2009 at 1:14 pm #

    @ John, a possible reason why this does not work is if you do not have IIS URL Authorization module installed and enabled. Open the Server Manager, go to Roles node and then find the Role Services list. In that list check if URL Authorization is installed.

  9. Gravatar ImageJohn responded on 09 Dec 2009 at 8:09 am #

    Ruslany,
    You were right, I did not have URL authorization enabled, and I got confused because I was using VS development server alongside IIS7 (http://www.4guysfromrolla.com/articles/122408-1.aspx cleared this up for me) and the behavior was different for each. So, I am beginning to understand now, but I have a follow up question. Does plugging the asp.net modules into the pipeline (i.e. removing and then adding FormsAuthenticationModule) also enable authentication on static content through the system.web authentication rule? If so, what is the advantage to using the system.webServer namespace? Thanks again.
    John

Trackback URI | Comments RSS

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

XML Markup: If You want to add XML code to the comment please XML encode it first, otherwise the code will not show up.

Recently Published Articles