Introduction

When trying to make a site resilient against denial-of-service attacks, you are limited in what you can do but one useful method is to block ip addresses temporarily if they are deemed to be making too many requests to your site either within a certain time frame or attempting too many concurrent requests against the same server. This might be malicious or accidental but either way it is useful to protected against this.

Downloading/Installing

Enter IIS Dynamic IP Security module from Microsoft, introduced in IIS8 by default but available in IIS7 and 7.5 as a downloadable extension.

What Does It Do?

The theory is easy. Configure how many requests can be made within a given time frame. Any more made in this time can return one of a number of error codes or simply drop the connection. You can also setup a maximum number of concurrent requests, which if exceeded returns the same error. You can choose one or the other or both and they live in the configuration under system.webserver/security/dynamicIpSecurity with some other options set under system.webserver/security/ipSecurity

Azure vs IIS

The problem with most of the help online is that it assumes you are directly accessing IIS on a server rather than deploying Azure web roles, where much of this is automated and to do it manually each time would be a pain. The steps required for Azure are:
  • Create a startup file in your role project (not in the cloud project), called whatever you like but startup.cmd is pretty standard. Click the properties for this file and ensure that "Copy to Output Directory" is set to Copy always or copy if newer.
  • Modify your ServiceDefinition.csdef file in your cloud project and add a Startup element under the WebRole for the role you are trying to setup. It can look like this:
  • <startup priority="-2">
    <task commandline="startup.cmd" executioncontext="elevated" tasktype="simple"></task>
    </startup>
  • It is important that the executioncontext is set to elevated otherwise the script will not be permitted to change the applicationHost.config file on the cloud server, which is where these settings end up.
  • Modify your startup.cmd and add in the calls (below) to command-line executables that will make the updates for you to the IIS configuration. We will use two commands, one will ensure the dynamic ip restrictions module is installed into IIS and the other (AppCmd) can be used to update the configuration files with the relevant sections.
  • NOTE: This instruction is for IIS which runs on Server 2012 and which includes a powershell cmdlet to install the IP restrictions module in IIS. I don't know if this is included in previous versions of Windows Server, and I've seen comments that a different script is required to install the (optional) module in IIS 7 and 7.5. The first command to add to startup.cmd is: PowerShell Install-WindowsFeature -Name Web-IP-Security
  • This feature includes both the static IP restriction functionality and the dynamic features. 
  • These next 3 stages are optional but sounds useful to me. It instructs IIS to do a little more work when working out the source ip address and not just looking at the request header which might contain a proxy ip address. This is handled in the ipSecurity section which must first be unlocked (it might already be but we do this just in case): %windir%\system32\inetsrv\AppCmd.exe unlock config /section:system.webServer/security/ipSecurity
  • Secondly, we ensure that an ipSecurity element is created (this will not overwrite the element if it is already there): %windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/security/ipSecurity /~ /commit:apphost
  • Thirdly, we add the child attributes we are interested in: %windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/security/ipSecurity /allowUnlisted:"true" /enableProxyMode:"true" /commit:apphost
  • The next stages are to enable the dynamic ip security following a similar idea but this example is done in a slightly different way (all merged into one). Firstly, the section unlock: %windir%\system32\inetsrv\AppCmd.exe unlock config /section:system.webServer/security/dynamicIpSecurity
  • And then add the element and it's children in one go: %windir%\system32\inetsrv\appcmd.exe set config -section:system.webServer/security/dynamicIpSecurity /denyByRequestRate.enabled:"True" /denyByRequestRate.maxRequests:"40" /denyByRequestRate.requestIntervalInMilliseconds:"5000" /denyByConcurrentRequests.enabled:"True" /denyByConcurrentRequests.maxConcurrentRequests:"10" /commit:apphost
  • Although you might prefer one method or the other, I've shown both because these are examples I found that work so I don't want to break it.
  • You can now deploy ready to test.

Testing

The obvious way to do testing is to set your limits quite low so they can easily be exceeded. I started with max requests of 10 every 5 seconds and max concurrent requests at 5. Once this is done, all you have to do is run some proxy tool like Fiddler to make the response codes obvious and then go to your site and start hitting lots of ctrl-F5s (or whatever does a force refresh of a page) and you should see the first requests return normally and then a bunch of 403s (the default code for failed IP addresses). After your timeout, it should start returning pages again but then block them. Note that you can increase the timeout and multiply your  requests (100 in 100 seconds instead of 10 in 10) which might be good in terms of making it harder for attackers to blast your site (it blocks them for longer) but this will also affect people who accidentally go over the limit and also increase the memory required for IIS to keep track of these requests so go carefully. Usually something is better than nothing - it doesn't have to be perfect.

Troubleshooting

This has become a mandatory heading for Azure articles!

Remote Desktop is kind of essential for debugging these types of issues. The questions are:
1) Is the module installed
2) Is the config correctly configured

Is the module installed?

This should have been taken care of by the first entry in your startup.cmd but before you try and install it via remote desktop, you can probably work out whether your startup .cmd has run correctly. Firstly, open a PowerShell command prompt and enter Get-WindowsFeature which will take a while and then display the list of available and installed windows features. Scroll up to near the top and ensure that the module called Ip and Domain Restrictions is shown as installed under Web Server (IIS) -> Web Server -> Security. If it is NOT, then either your startup.cmd did not run or it ran but your command was not correct. If it is installed move onto the next section "is the config correctly configured".

Copy the text that installs the module from your startup.cmd and paste it into the PowerShell terminal and press enter.  If this succeeds then either your startup.cmd is failing before it gets to this command (try pasting in the previous command in order, one at a time) or your startup.cmd is simply not running at all. Also, possibly, you have got confused between the server that is being deployed to and the server you are testing. If pasting this into PowerShell does NOT work, then you should get either an error due to something you need to fix on the box (unlikely), or your command is not typed correctly and you need to fix it in startup.cmd.

NOTE: If for some reason, your startup.cmd didn't run either at all or if it failed part-way through, you should re-deploy your project after fixing it otherwise the AppCmd entries will not have been run and will not have completed the installation.

Is the Config Correctly Configured?

If you didn't change the example above, the configuration will be written to d:\windows\system32\inetsrv\ApplicationHost.config. In my case, the dynamic element was right at the bottom and the ipsecurity element was near the top but if you open that file and search for those elements first then ask did one or both get written and do they appear to be OK. If one or both are missing then the section might not have been unlocked and/or the command to add the elements might be wrong or might have failed. Copy your startup.cmd commands that use appcmd.exe one at a time into powershell and replace %windir% with d:\windows. At each point, check that you don't get an error and that the command runs correctly.

If it very unlikely that your startup.cmd ran OK and the commands don't cause an error because otherwise they would have added the configuration. The most likely problem if you don't get errors is that the startup.cmd failed mid-way through and didn't run your AppCmd entries in the first place.

If the entries were added but they look incorrect, then you have probably just mis-typed something in the command, take a close look and make sure they look correct. If you are not sure what they should look like, use IIS on your server to setup the values that you want to use and then open applicationHost.config to see what IIS wrote to file.