WordPress login and xmlrpc.php IIS restrictions

wp-login.php, /wpadmin and xmlrpc.php are frequently targeted by bots in brute force attacks.  Even if the site is secured to prevent the brute force attacks from succeeding, a common result of the repeated requests is the site will see a CPU spike causing it to become much slower to respond or it will error out with a "500.0 The FastCGI process exited unexpectedly".  Within IIS, restricting access can be accomplished via the ipSecurity element.  Implemented by updating the WordPress web.config file.  With a complete example web.config found at the bottom of this article.

Note that these updates only prevent access to files after a request has already reached the server.  To monitor and stop the requests themselves, a service like cloudflare should also be considered.


wp-login.php and /wpadmin

First obtain the IP address of any user that needs to have access to the admin section of WordPress.  When not known, simply Google "my ip".  From there add two new sections to the configuration element of the web.config as in the example below

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <location path="wp-login.php">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="xx.xx.xx.xx" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

  <location path="wp-admin">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="xx.xx.xx.xx" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

Replacing the ""xx.xx.xx.xx" with the IP address of the user.  If there is more than one user, simply copy and paste as many "add ipAddress" lines as necessary below those in the example.


xmlrpc.php

xmlrpc.php is a WordPress API that is being phased out, but most notably is still used for WordPress' own JetPack plugin.  The same general syntax can be used to deny access to xmlrpc.php.  However, if using JetPack, then it's necessary to allow WordPress IP ranges found at https://jetpack.com/support/hosting-faq/#jetpack-whitelist

When not using a plugin like JetPack that requires whitelisting, add a new web.config section like the example below

  <location path="xmlrpc.php">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

Because JetPack requires an IP range be whitelisted, rather than a single IP address, additional syntax is required in the form of "subnetMask".  To get a subnet mask from a CIDR like those provided by JetPack, a converter like https://www.ipaddressguide.com/cidr may be used.

Using 122.248.245.244/32 as an example, the syntax for adding the IP address and subnet mask is

<add ipAddress="122.248.245.244" subnetMask="255.255.255.255" allowed="true" />

Then an example of the full section being

  <location path="xmlrpc.php">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="122.248.245.244" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="54.217.201.243" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="54.232.116.4" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.80.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="192.0.96.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="192.0.112.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="195.234.108.0" subnetMask="255.255.252.0" allowed="true" />
          <add ipAddress="192.0.96.202" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.98.138" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.102.71" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.102.95" subnetMask="255.255.255.255" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>


Example WordPress web.config with Permalinks

An example of a full WordPress web.config with the above restrictions, JetPack whitelisted IPs and WordPress permalinks rewrite rule
                    
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

  <location path="wp-login.php">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="xx.xx.xx.xx" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

  <location path="wp-admin">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="xx.xx.xx.xx" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>
  
  <location path="xmlrpc.php">
    <system.webServer>
      <security>
        <ipSecurity allowUnlisted="false">
          <add ipAddress="122.248.245.244" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="54.217.201.243" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="54.232.116.4" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.80.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="192.0.96.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="192.0.112.0" subnetMask="255.255.240.0" allowed="true" />
          <add ipAddress="195.234.108.0" subnetMask="255.255.252.0" allowed="true" />
          <add ipAddress="192.0.96.202" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.98.138" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.102.71" subnetMask="255.255.255.255" allowed="true" />
          <add ipAddress="192.0.102.95" subnetMask="255.255.255.255" allowed="true" />
        </ipSecurity>
      </security>
    </system.webServer>
  </location>

  <system.webServer>
    <rewrite>
      <rules>
        <rule name="WordPress Rule" stopProcessing="true">
          <match url=".*" />
            <conditions>
              <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
              <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
            </conditions>
          <action type="Rewrite" url="index.php" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>