[Maarten Van Horenbeeck] [Information Security] [Resources]

Fingerprinting a web daemon using HTTP requests

This story starts with a very simple idea. How do web servers respond when you send a HTTP request. Do they respond differently based on how the request is posed ? Would it be possible to ascertain the brand of web server by doing this ? I had been working with this idea for a few months at the beginning of 2004, and it was first mentioned during a mailing list conversation with Marc Ruel in November of that year. Saumil Shah, the author of the great tool httpprint had already incorporated in his tool a check for a non-existent HTTP version. What we're trying to do is however to have a look at how servers respond to existent, but perhaps malformed, or even completely legitimate headers.

I regularly do some experiments and testing on this, and post my results here. This may be useful during penetration tests, in order to identify a web server that may have his banner altered or perhaps does not even return a Server: header.

Quoting myself in a November 2004 full-disclosure message:

In RFC 2616, describing version 1.1 of the Hypertext Transfer Protocol, it 
is described that the specification expects HTTP/1.1 servers to respond 
appropriately with a message in "the same major version used by the 
client". However, this is not in compliance with another RFC, 2145, 
which explicitly states that a server should send the highest version it 
supports, but "may" send a lower version in case it is suspected that the 
client may not handle the higher version correctly. 


This means that an HTTP/0.9 request is usually responded to with an HTTP/0.9 reply. An HTTP/1.0 request can be responded to with either an HTTP/1.0 or HTTP/1.1 reply. This is done because in versions prior to "major version" 1, no version numbers where used, which would make it harder for a 0.9 version to identify the server side.

Sending a regular HTTP/1.0 request

Now, let's see how a number of mainstream webservers respond to a HTTP/1.0 request. We all know HTTP/1.1 is by far the most common, but since there is room within the RFC to "wiggle" a little bit, there may be some uniqueness here.

Apache 1.3

[maarten@mojave maarten]$ telnet [apache] 80
Trying 127.0.0.1...
Connected to [apache].
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.1 200 OK
Date: Wed, 24 May 2006 14:37:51 GMT
Server: webserver
Apache 1.3 responds with a HTTP/1.1 response.

IIS 6.0

Connected to [IIS6.0].
Escape character is '^]'.
GET / HTTP/1.0
Host: [IIS6.0]

Connection closed by foreign host.
HTTP/1.1 302 Object moved
Connection: close
IIS 6 responds with an HTTP/1.1 response.

Netscape-Enterprise/6.0

[maarten@mojave maarten]$ telnet [Netscape] 80
Trying 172.0.0.17...
Connected to [Netscape].
Escape character is '^]'.
GET / HTTP/1.0
Host: [Netscape]

HTTP/1.1 301 Moved Permanently
Date: Wed, 24 May 2006 14:52:26 GMT
Location: http://wwws.sun.com/software
Connection: close
Content-Type: text/html; charset=iso-8859-1
Netscape also responds with an HTTP/1.1 response.

Publicfile

Connected to www.daemon.be.
Escape character is '^]'.
GET / HTTP/1.0
Host: www.daemon.be

HTTP/1.0 200 OK
Server: publicfile
Date: Wed, 24 May 2006 14:53:20 GMT
Last-Modified: Sat, 21 Jan 2006 22:45:03 GMT
Content-Type: text/html
Content-Length: 6483
Aha, how unique. Publicfile responds with an HTTP/1.0 response.

Sending a broken HTTP/0.9 request

What would happen if we try to send a reply of the HTTP/1.0 format, but with a version id of HTTP/0.9? I sent such a request string to a number of different servers.

Apache 1.3

[maarten@mojave maarten]$ telnet [apache] 80
Trying 127.0.0.1...
Connected to [apache].
Escape character is '^]'.
GET /index.html HTTP/0.9

HTTP/1.1 200 OK
Date: Wed, 24 May 2006 14:12:34 GMT
Server: Apache 1.3.24 
Apache 1.3.24 obviously interprets it as a HTTP/1 request, as I even have to press enter oncer where normally I would be sending a Host: header.

IIS 6.0

[maarten@mojave maarten]$ telnet [IIS6.0] 80
Trying 172.0.0.16...
Connected to [IIS6.0].
Escape character is '^]'.
GET / HTTP/0.9
HTTP/1.1 400 Bad Request
Content-Type: text/html
Date: Wed, 24 May 2006 14:26:41 GMT
Connection: close
Content-Length: 20
Microsoft's IIS 6.0 interprets it as being a "faulty" request. It doesn't wait for the Host: header and immediately returns an error.

Netscape-Enterprise/6.0

[maarten@mojave maarten]$ telnet [Netscape] 80 | more
Trying 172.0.0.17...
Connected to [Netscape].
Escape character is '^]'.
GET / HTTP/0.9

HTTP/1.1 301 Moved Permanently
Date: Wed, 24 May 2006 14:28:38 GMT
Location: http://java.sun.com/
Connection: close
Content-Type: text/html; charset=iso-8859-1
Netscape shows the same behaviour as Apache here.

Publicfile

[maarten@mojave maarten]$ telnet www.daemon.be 80
Trying 84.16.244.210...
Connected to www.daemon.be.
Escape character is '^]'.
GET / HTTP/0.9

HTTP/1.1 400 HTTP/1.1 requests must include a host name
Server: publicfile
Date: Wed, 24 May 2006 14:34:58 GMT
Content-Length: 70
Connection: close
Content-Type: text/html
Publicfile is really polite here. It supports HTTP/0.9 natively, and responds correctly to such queries. However, when sending a request that states 0.9 as a version number (malformed) it replies that version 1.1 requests require a host name and discards the connection.