Using Enhanced Request Filtering Features in IIS7

by Suditi Lahiri

This article provides a list of common usage scenarios for enhanced Request Filtering features, which is shipped with Windows Server 2008 SP2 or can be downloaded from https://www.microsoft.com/downloads/ for Windows Server 2008 RTM. In the absence of a corresponding UI to configure these features, appcmd.exe is used to enable and configure the scenarios in this article.

Creating Rules to Disallow String Patterns in Parts of Requests

A new feature, added to IIS Request Filtering feature, is the ability to create a rule list that will let you specify rules to disallow requests based on patterns matched against certain portions of an HTTP request. The main configuration for this is the filteringRules section under the system.webServer/security/requestFiltering section. In the event of a denied condition HTTP Error 404.19 is raised.

Example

In this example a server administrator wishes to block strings "Foo" and "Bar" in header "Foo-Header" using appcmd.exe:

appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockFooInHeader']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockFooInHeader'].scanHeaders.[requestHeader='Foo-Header']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockFooInHeader'].denyStrings.[string='Foo']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockFooInHeader'].denyStrings.[string='Bar']"

Config

<configuration> 
  <system.webServer>
    <security>
      <requestFiltering>
        <filteringRules>
          <filteringRule name="BlockFooInHeader">
            <scanHeaders>
              <add requestHeader="Foo-Header" />
            </scanHeaders>
            <denyStrings>
              <add string="Foo" />
              <add string="Bar" />
            </denyStrings>
          </filteringRule>
        </filteringRules>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

The request below would fail this rule since it has a header called Foo-Header that contains the 'Foo' pattern

GET /iisstart.htm HTTP/1.1\r\n
Host: localhost\r\n
Accept: */*\r\n
Foo-Header: Foo--value\r\n
\r\n

Example

In this example a server administrator wishes to block strings "Insert" and "Table" in the query string sent with any ".asp" page:

appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockSqlCommands',scanQueryString='True']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockSqlCommands',scanQueryString='True'].appliesTo.[fileExtension='.asp']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockSqlCommands',scanQueryString='True'].denyStrings.[string='Insert']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"filteringRules.[name='BlockSqlCommands',scanQueryString='True'].denyStrings.[string='Table']"

Config

<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <filteringRules>
          <filteringRule name="BlockSqlCommands" scanQueryString="true">
            <appliesTo>
              <add fileExtension=".asp" />
            </appliesTo>
            <denyStrings>
              <add string="Insert" />
              <add string="Table" />
            </denyStrings>
          </filteringRule>
        </filteringRules>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

The asp request below would fail this rule since it has string "Insert" in Query String:

GET /iisstart.asp?Query=InsertData HTTP/1.1\r\n
Host: localhost\r\n
Accept: */*\r\n
Foo-Header: Foo--value\r\n
\r\n

It is important to note that the whole header will be scanned for the string patterns listed under "denyString" section and not the header value only.

Creating Safe-List for URLs and Query Strings

This new feature allows you to specify safe URLs and query strings that will bypass all the deny rules defined. This feature must be used with caution, since wrong configuration in this section could let malicious requests bypass your deny rules. If a user always wants to allow the URL '/my.login.page.asp' for instance even though it might trigger a deny rule defined, you can add configuration as below to allow this.The feature can be configured using alwaysAllowedUrls and alwaysAllowedQueryStrings under system.webServer/security/requestFiltering section.

Example

In the following example appcmd.exe is used to configure page "Login.asp" as an approved URL and "Allow=true" as a approved Query string sequence.

appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"alwaysAllowedUrls.[url='Login.asp']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"alwaysAllowedQueryStrings.[queryString='Allow=true']"

Config

<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <alwaysAllowedUrls>
          <add url="Login.asp" />
        </alwaysAllowedUrls>
        <alwaysAllowedQueryStrings>
          <add queryString="Allow=true" />
        </alwaysAllowedQueryStrings>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

It is important to note that the leading '/' is required for the URL to be accepted as a valid URL. If a user wants to allow a query string 'session<1' which might otherwise trigger a deny rule defined, you can add configuration as below to allow this. Note that you do not need to specify query strings leading with the '?' character.

Creating a Deny List of URL Sequences

To deny a list of URL sequences for all requests create a denyQueryStringSequences section and add the list of strings you want to disallow in the URL of your requests. The deny list is case insensitive and allows encoded values of the format %XX, where XX are hexadecimal digits. In the event of a denied condition HTTP Error 404.18 is raised.

Example

For example, if you wanted to deny any URL with ".." or "./" sequence, appcmd.exe command will be:

appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"denyQueryStringSequences.[sequence='..']"
appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /+"denyQueryStringSequences.[sequence='./']"

Config

<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <denyQueryStringSequences>
          <add sequence=".." />
          <add sequence="./" />
        </denyQueryStringSequences>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

The request below would fail this rule since it has a ".." in its Query String:

GET /iisstart.htm?.. HTTP/1.1\r\n
Host: localhost\r\n
Accept: */*\r\n
\r\n

Checking for both Escaped and unEscaped Query String

It is possible that you want to scan for both escaped and un-escaped versions of this pattern using unescapeQueryString attribute in the "system.webServer/security/requestFiltering" section.

Example

So if someone were to send a request like http://www.foo.com/id=%3C%53%43%52%49%50%54%3E where the <script> sequence has been escaped, we would like to check the un-escaped version of this query string as well:

appcmd.exe set config "Default Web Site" -section:system.webServer/security/requestFiltering /unescapeQueryString:"True"

Config

<configuration>
  <system.webServer>
    <security>
      <requestFiltering unescapeQueryString="true">
        <denyQueryStringSequences>
          <add sequence="script" />
        </denyQueryStringSequences>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

This will perform 2 passes when it checks for patterns in query string, one for raw query string and one for the un-escaped query string.