On IIS 6, Content-Location, Internal IP Addresses, and URL Redirection
One of our automated security scans of our Web site turned up a low priority notice that our server was revealing its internal IP address on some redirect requests. It’s a “feature” of IIS that occurs when a client requests a resource via HTTP 1.0 (or without the Host header) that results in a redirection. The output of a telnet session to such a resource looks something like this:
[npiaseck ~]$ telnet www.example.com 80 Trying 10.1.10.38... Connected to www.example.com (10.1.10.38). Escape character is '^]'. GET /Media HTTP/1.0 HTTP/1.1 301 Moved Permanently Content-Length: 152 Content-Type: text/html Location: http://10.1.10.38/Media/ Server: Microsoft-IIS/6.0 Date: Fri, 05 Dec 2008 13:50:00 GMT Connection: close <head><title>Document Moved</title></head> <body><h1>Object Moved</h1> This document may be found <a HREF="http://10.1.10.38/Media/">here</a> </body> Connection closed by foreign host.
I’ve made up an internal IP address, but the point is that IIS is inserting the internal IP address in the body of the document as well as the Location header. If you make the same request with the Host header supplied, such as in Host: www.example.com, then IIS would use www.example.com instead of supplying the IP address.
The recommended Microsoft solution is detailed in KB834141. Namely, you edit the SetHostName metabase entry for the Web site to www.example.com, and now IIS will use that instead of the IP address in the above redirection scenario. Easy as pie, right?
Well, that change broke some code. For reasons that are not clear to me, here’s the mechanism by which IIS 6 determines what to use in these redirect requests:
- Is the
UseHostNameproperty set totrue? If so, use the machine’s host name. - Is the
SetHostNameproperty set to some value? If so, use that value. - Is a
Hostheader supplied in the HTTP request? If so, use that value. - If all of the above fail, just use the IP address, which is probably internal.
For search engine optimization reasons, all of the requests on our Web site redirect all requests that come in without the www subdomain to ones with the www subdomain. This prevents Google from dinging us for having duplicated content. The code was implemented as an ASP.NET HTTP module that ran before every request and inspected the incoming URL by looking at the HttpContext.Current.Request.Url.OriginalString property. If the domain didn’t match, then it issued a permanent redirect.
The problem with the above precedence order is that IIS will substitute the value of HTTP_HOST and other server variables passed into ASP.NET with the SetHostName value, not the Host header if so supplied. The net effect is that our redirection method suddenly stopped working because the code was always seeing the SetHostName value, www.example.com, regardless of the URL that was actually used to make the request. To make matters worse, if we were to set the UseHostName property, the code would always see a mismatch and enter a redirect loop. Neither are desirable scenarios.
The solution is to change the code to inspect the Host header directly, if present, instead of looking at the URL as passed into the server variables array. It ends up looking something like this:
private void OnContextBeginRequest(object sender, EventArgs e) { HttpContext context; string originalRequestHost; int portIndex; HttpRequest request; string redirectUri; context = HttpContext.Current; request = context.Request; originalRequestHost = request.Headers["Host"]; // Some clients will pass in the port number in the Host header if // it's not going over port 80 portIndex = originalRequestHost.LastIndexOf(':'); if (portIndex != -1) { originalRequestHost = originalRequestHost.Substring(0, portIndex); } if (originalRequestHost != null && originalRequestHost != mPreferredSubdomain) { redirectUri = string.Format( "http://{0}{1}{2}", mPreferredSubdomain, request.Url.Port != 80 ? ":" + request.Url.Port.ToString() : string.Empty, request.RawUrl); cLog.InfoFormat("Redirecting from to {0} (domain: {1}, preferred: {2}).", redirectUri, originalRequestHost, mPreferredSubdomain); context.Response.Redirect(redirectUri, true); } }
The code looks at the host header. If it’s set, and doesn’t match, then it redirects. (It does a little bit of finagling to strip off the port number if a non-standard port is used in the request.)
With this code in place, we can now use the SetHostName property and still have our URL redirection work properly for HTTP/1.1 requests. We’re out of luck when it comes to HTTP/1.0 requests, but this is something that we can live with.






Your blog has some great information for small businesses. Thanks for taking the time to share your knowledge!
Thank you very much. It helped us out.