IIS web-servers running in Windows Azure may reveal their private IP for certain requests.

Internet Information Services (the handy web-server from Microsoft) runs on Windows server OS but also in the Microsoft Azure Cloud. If you are building virtual machines and deploying them to the cloud (IAAS – Infrastructure as a Service) or using Cloud Services from Windows Azure (PAAS – Platform as a Service), you will basically be using an IIS web-server in behind the scenes to host your service.

When deployed inside Windows Azure, the virtual machines (IAAS or PAAS) that are running your IIS server are allocated private IP addresses. Windows Azure does the job of forwarding traffic from the public IP address and port that you are using to the private IP address and port combination of the virtual machine(s) that you are hosting in the cloud. The scenario diagram looks a bit like the one shown below:

You request a resource from you service in Azure, this is routed to the public IP address the service is hosted on. The request is then routed further by Windows Azure to the private IP address of (one of) the server(s) that host this service. The details of how this is done is beyond the purpose of this blog.

What is interesting to note is that we can send some requests to the IIS server which will make it respond with the internal (private IP) that the server has in the Cloud. Some may consider this a disclosure of information that is not intended for the end client, so we may wish to mitigate against this disclosure, but let's first try to understand what happens.

HTTP (Hyper text transfer protocol) currently supports three versions:

  • Version 1.0 of the protocol specification (the original version)
  • Version 1.1 of the protocol (which is the most widely used)
  • Version 2.0 which is starting to gain traction in today's web-server world.

The requests that are problematic for this scenario are all sent using the HTTP 1.0 version of the protocol. There are a couple of differences between HTTP 1.0 and HTTP 1.1 but the one that we are interested in here is the fact that in HTTP 1.0 we did not have to specify a 'host' http-header in the request.

When sending http requests to a server, we usually type in the name of the site / service we are trying to reach. You would type in https://www.linqto.me
should you be trying to reach my bookmarking service. The 'www.linqto.me' is the host name, which will be resolved by the browser to the server's public IP address. Hence the request to the site would look something like this:

GET / HTTP/1.1
Accept    text/html, application/xhtml+xml, */*
Accept-Encoding    gzip, deflate
Accept-Language    fr-FR,en-US;q=0.5
Connection    Keep-Alive
Host    www.linqto.me
User-Agent    Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko

Note that Host http-header in the sample above is set to www.linqto.me. However, to open the TCP connection, the browser also needs to resolve this name into the IP address of the server – which it will do under the covers.

In HTTP 1.0, it is possible to instruct a client (browser or other) to open a TCP connection to a web-server and send a request without sending the Host header, and only send the name of the requested resource (which is / (slash) in the above example). In the response from the server contains a redirection to another page or resource (http status codes 301 or 302), the server will specify where the client should redirect to via a Location http header. This header will contain the name of the internal IP of the IIS server if no host http-header is provided for the request.

Why is this happening?

When performing a redirect, IIS needs to tell the connecting client where to look for the resource. It needs to build the url for the redirection based on information it has: the incoming request and the settings for the IIS web-site. Since the incoming request does not contain a host header (like in the case of the HTTP 1.0 request), and the IIS website does not have any host header mappings setup (and is basically listening for all HTTP traffic on an IP and port combination), the only thing it can be sure of is the IP and port address combination the site is bound to.

It cannot do any reverse DNS lookups – since traffic it forwarded to the private address of the server by Windows Azure (and IIS does not do reverse IP DNS queries anyways). Hence, to build the URL for the redirect it needs to perform, it will use the private IP address of the server inside Windows Azure when it issues the response.

You can see this issue happening when using a tool like curl to issue an HTTP 1.0 request. Consider the following command in curl:

curl https://xxx.xxx.xxx.xxx/somepage.aspx --http1.0 --header "Accept:" --header "Connection:" --header "Host:" -i -v

Where xxx.xxx.xxx.xxx is the public IP address of the service running in azure, and the /somepage.aspx would result in a 301 or 302 redirect status code from the server. Since we are setting the Host header to null when sending the request, and we are indicating we are using http1.0, we will not be sending any information about the name of the site to the IIS server.

How can we work around this issue?

There are a couple of ways to work around the problem. The first is to define an alternateHostName for the server or the site you wish to protect. Here are the appcmd or powershell commands you can use to set this parameter up:

For a website:

"[SiteName]" -section:system.webServer/serverRuntime /alternateHostName:"[AltHostName]"  /commit:apphost

'MACHINE/WEBROOT/APPHOST' -location '[SiteName]'
"alternateHostName" -value "[AltHostName]"

For the entire server:

config  -section:system.webServer/serverRuntime /alternateHostName:"[AltHostName]"  /commit:apphost

"alternateHostName" -value "[AltHostName]"

You may also use the configuration editor in IIS, and navigate into the system.WebSerer/ServerRuntime tag and set the value as shown below:

This will give the server an extra piece of information on how to deal with the response url it needs to build. Instead of just basing itself on the IP address of the site's bindings, it will use the alternateHostName value if it is provided.

The other way in which you can work around this issue is to use Url Rewrite to deny HTTP 1.0 traffic to your site. You can download url rewrite for the IIS.net site here: https://www.iis.net/downloads/microsoft/url-rewrite.

You will need to configure a new 'Inbound Rule' in url rewrite. Name the rule something meaningful like 'Block HTTP 1.0 traffic' and then set the match type to 'Math URL'. The Requested URL should be 'Matches the Pattern' and the pattern type should be 'Wildcards' and the pattern should be '*' to trap all incoming requests.

In the conditions part of the rule configuration, you need to add a condition to match the {SERVER_PROTOCOL} variable to the 'HTTP/1.0' pattern. The SERVER_PROTOCOL variable will be populated by the IIS server based on the HTTP version specified in the incoming request.

Finally, the action that should be taken when such a request is detected is to 'Abort Request', essentially closing the TCP connection to the client by sending back a TCP RST (reset) on the connection. The entire configuration is in the screenshot below:

When looking into the web.config of the site, the resulting Url Rewrite rule is the following:

<rule name="Block HTTP 1.0 Rule" patternSyntax="Wildcard" stopProcessing="true">
<match url="*" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{SERVER_PROTOCOL}" pattern="HTTP/1.0" />
<action type="AbortRequest" />

For this rule to work, you will need to make sure that it is the first rule listed inside the inbound rules section for your website or web-application in the Url Rewrite section:

If this is not the case, you can use the 'Move Up' action button on the right hand side of the IIS console to make sure the rule is the first one to be interpreted on any incoming request, essentially blocking all HTTP 1.0 traffic to the site.

By Paul Cociuba