There was a great email posted to the ModSecurity user mail-list today that asked about ModSecurity's ability (or inability) to trick web server fingerprinting tools such as HTTPrint. The short answer is YES, ModSecurity 2.X can be used to effectively ruin the accuracy of HTTPrint. The most important point here is that ModSecurity 2.X now has a hook in to the Apache PostReadRequest portion of the request cycle (phase:1) where previously it would run much later in the Fixup phase (phase:2).
There are many different possibilities for mitigating the effectiveness of these types of fingerprinting scanners. For complete information, I suggest you read the http fingerprinting Appendix section I wrote as part of the WASC Threat Classification document.
HTTPrint a not a typical "banner grabbing" application, as it has more logic to it. It's main fingerprinting technique has to do with the Sematic differences in how web servers/applications respond to various stimuli. Let's take a look.
If I run HTTPrint v0.301 (the most recent version) against a default Apache 2.2.3 web server, it reports the following:
$ ./httprint -h 192.168.10.27 -s signatures.txt
httprint v0.301 (beta) - web server fingerprinting tool
(c) 2003-2005 net-square solutions pvt. ltd. - see readme.txt
http://net-square.com/httprint/
httprint@net-square.com
Finger Printing on http://192.168.10.27:80/
Finger Printing Completed on http://192.168.10.27:80/
--------------------------------------------------
Host: 192.168.10.27
Derived Signature:
Apache/2.2.0 (Fedora)
9E431BC86ED3C295811C9DC5811C9DC5050C5D32505FCFE84276E4BB811C9DC5
0D7645B5811C9DC5811C9DC5CD37187C11DDC7D7811C9DC5811C9DC58A91CF57
FCCC535B6ED3C295FCCC535B811C9DC5E2CE6927050C5D336ED3C2959E431BC8
6ED3C295E2CE69262A200B4C6ED3C2956ED3C2956ED3C2956ED3C295E2CE6923
E2CE69236ED3C295811C9DC5E2CE6927E2CE6923
Banner Reported: Apache/2.2.0 (Fedora)
Banner Deduced: Apache/2.0.x
Score: 140
Confidence: 84.34------------------------
Scores:
Apache/2.0.x: 140 84.34
Apache/1.3.[4-24]: 132 68.91
Apache/1.3.27: 131 67.12
Apache/1.3.26: 130 65.36
Apache/1.3.[1-3]: 127 60.28
--CUT--
As you can see, it correctly fingerprinted my Server as an Apache 2.X server. The only reason that it wasn't any more accurate was that it didn't have a signature for 2.2.3 yet (but that is easily fixed by pasting the fingerprint above into the signatures.txt file with the proper label). Anyways, after running this scan, my Apache logs show this info:
192.168.10.69 - - [15/Jan/2007:12:26:20 -0500] "\x16\x03" 501 214
192.168.10.69 - - [15/Jan/2007:12:26:20 -0500] "GET / HTTP/1.0" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/1.0" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "OPTIONS * HTTP/1.0" 200 -
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "OPTIONS / HTTP/1.0" 200 -
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET /antidisestablishmentarianism HTTP/1.0" 404 226
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "PUT / HTTP/1.0" 405 231
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "JUNKMETHOD / HTTP/1.0" 501 222
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / JUNK/1.0" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "get / http/1.0" 501 215
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "POST / HTTP/1.0" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET /cgi-bin/ HTTP/1.0" 403 210
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET /scripts/ HTTP/1.0" 404 206
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/0.8" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/0.9" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/1.1" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/1.2" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/1.1" 400 226
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/1.2" 400 226
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET / HTTP/3.0" 200 44
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET /.asmx HTTP/1.1" 404 203
192.168.10.69 - - [15/Jan/2007:12:26:21 -0500] "GET /../../ HTTP/1.0" 400 226
What is important to notice are the various HTTP Response Codes that Apache trigger based on the different malformed requests. There were 501s, 400s and some 200s.
Now, if we want to use ModSecurity to combat HTTPrint, we need to use signatures that enforce HTTP Compliance as this is the core of HTTPrint's Semantic tests. Fortunately, the ModSecurity Core Rules come with many different rules that will help to enforce HTTP compliance. After impelmenting ModSecurity + the Core Rules, if I re-run HTTPrint you will see that it isn't even able to complete it's tests and it errors out:
$ ./httprint -h 192.168.10.27 -s signatures.txt
httprint v0.301 (beta) - web server fingerprinting tool
(c) 2003-2005 net-square solutions pvt. ltd. - see readme.txt
http://net-square.com/httprint/
httprint@net-square.com
Finger Printing on http://192.168.10.27:80/
Finger Printing Completed on http://192.168.10.27:80/
--------------------------------------------------
Host: 192.168.10.27
Fingerprinting Error: Invalid response from server, check configuration...
--------------------------------------------------
Now, back in the Apache access log file you will see that HTTPrint actually sent only 2 requests and ModSecurity responded with status codes of 400 for both:
# cat access_log
192.168.10.69 - - [15/Jan/2007:12:57:27 -0500] "\x16\x03" 400 226
192.168.10.69 - - [15/Jan/2007:12:57:27 -0500] "GET / HTTP/1.0" 400 226
The error_log shows why ModSecurity blocked the requests:
[Mon Jan 15 12:57:27 2007] [error] [client 192.168.10.69] ModSecurity: Access denied with code 400 (phase 1). Operator EQ match: 0. [id "960008"] [msg "Request Missing a Host Header"] [severity "WARNING"] [uri ""] [unique_id "@om-EcCoChsAABmtbhgAAAAA"]
[Mon Jan 15 12:57:27 2007] [error] [client 192.168.10.69] ModSecurity: Access denied with code 400 (phase 1). Operator EQ match: 0. [id "960015"] [msg "Request Missing an Accept Header"] [hostname "192.168.10.27"] [uri "/"] [unique_id "@onadMCoChsAABmubtAAAAAB"]
As you can see, HTTPrint did not send certain mandatory requests headers (host and accept) so ModSecurity blocked the requests. Now, due to the fact that these first few requests sent by HTTPrint are supposed to be used to baseline normal response, the status codes of 400 were not expected and it therefore errored out.
Even if I remove these 2 Core Rules signatures, there are other rules that would still trigger block the requests. These include the check for a User-Agent request header, verifying that the Host header is an actual name and not an IP address (as that is normally indicative of worm activity, there are also other signatures that can enforce only the GET|HEAD|POST request methods and ensure that the request ends in HTTP and one of the legitimate protocol numbers (0.9, 1.0 or 1.1). With this layered approach, ModSecurity can provide effective defenses against the HTTPrint scanner.