Integrating Passive Fingerprinting into PHP/Apache

p0f is a passive OS fingerprinting tool by Michal Zalewski (author of the mighty Silence on the Wire). It's passive, because unlike traditional fingerprinting tools (such as Nmap), it doesn't generate any network traffic, instead making its predications by observing legitimate traffic flowing from the target. As such, the accuracy isn't as high as Nmap*, but it can still provide plenty of useful information.

Why would you want to integrate p0f into your webserver/website? Curiousity, nosiness, tracking troublemakers. Most browsers allow you to change their user agent string, so the Apache access log isn't a foolproof way of determining the OS that a user is on. As well as providing a more reliable identification of the OS, p0f can also give you information on the user's network (link type, NAT) and - somewhat unreliably - uptime.

The first step is to launch p0f in daemon mode. The options below cause it to answer requests via the socket /var/run/p0f/p0f.sock, drop privileges to the user apache is running as ('apache' in this case), and run in quiet mode:

p0f -qKU -Q /var/run/p0f/p0f.sock -u www-data

(You may also want to pass a filter string to limit the ports/interfaces that p0f listens on. If the apache user is compromised, this will reduce the extent to which an attacker query p0f himself.)

p0f will now sit in the background monitoring traffic, and storing fingerprinted hosts in a cache. The cache can be queried by sending a request (containing the IP and port for both source and destination) to the socket.

Querying the Cache in PHP

The p0f source tarball contains a sample perl client for querying the cache, but it's also easy enough to do in PHP. Just a case of packing the query, and unpacking the response:

<?

$fd = fsockopen("unix:///var/run/p0f/p0f.sock");

$query = pack("LLLNNSS", 0x0defaced, 1, 0x12345678, ip2long($_SERVER['REMOTE_ADDR']), ip2long($_SERVER['SERVER_ADDR']), 
		$_SERVER['REMOTE_PORT'],  $_SERVER['SERVER_PORT']);

fwrite($fd, $query);
$r =  fgets($fd, 1024);
fclose($fd);

$resp = unpack("Lmagic_number/Lid/Ctype/a20genre/a40details/cdistance/a30link/a30tos/Cfw/Cnat/Creal/sscore/Smflags/Nuptime", $r);

if ($resp['type'] == 1) {
        print "Bad magic number";
} else if ($resp['type'] == 2) {
        print "Connection not found in cache (expired? increase cache size?)";
} else {
        print_r($resp);
}

?>

... and here's the output. Hopefully it should closely match your platform.



The uses for this seem rather limited though (apart from freaking out visitors to your site). Much more useful would be to add this information to Apache's access log...


Intergrating into Apache's Logs

The obvious solution would be to use Apache's ability to send long events to a script, then get that script to query the p0f daemon before writing the logs out to a file. It doesn't seem like a very elegant of efficient solution though. It'd be much more fun if the p0f response were available as a series of env vars which could be used in the LogFormat directive ...

mod_pof

The solution is mod_pof (note the penultimate character is a oh, not a zero), an Apache 2 module which queries the p0f daemon for each incoming request, and sets a handful of env vars with the response. These variables can be used in LogFormat by referencing them as %{VAR_NAME} or via CGI scripts - in PHP they are part of the $_SERVER array, eg:


print_r($_SERVER);


Array
(
    [pof_type] => 0
    [pof_genre] => Linux
    [pof_details] => 2.6 (newer, 3)
    [pof_distance] => 2
    [pof_link] => ethernet/modem
    [pof_tos] => 
    [pof_uptime] => 17
    [pof_score] => 100
    [pof_mflags] => 
    [pof_fw] => 
    [pof_nat] => 
    [HTTP_HOST] => 192.168.10.2
    [HTTP_USER_AGENT] => Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1.14) Gecko/20080404 Iceweasel/2.0.0.14 (Debian-2.0.0.14-2)
    [HTTP_ACCEPT] => text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
    ....

In the Apache config, you could use something like:


LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" \"%{pof_genre}i\" \"%{pof_details}i\" combined-custom
CustomLog /var/log/apache2/cag.access.log combined-custom

Installing

The source code is available from http://linuxbox.co.uk/mod_pof-0.1.tar.gz. The module looks for the p0f socket at /var/run/p0f/p0f.sock; if this isn't the location you're using, edit mod_pof.c accordingly (there's only one instanced to be edited). To compile (sorry, no fancy makefile), first grab a copy of p0f-query.h, config.h and types.h from the p0f tarball, and put them in the same directory as mod_pof.c, then:

gcc -fPIC -DSHARED_MODULE -I/usr/include/apache2 -I/usr/include/apr-1.0  -c mod_pof.c
ld -Bshareable -o mod_pof.so mod_pof.o

... then copy into your Apache modules directory (location varies):

cp mod_pof.so /usr/lib/apache2/modules/

, and restart Apache. Any errors communicating with the p0f socket will be stored in the pof_err env var, accessible as described above.

Notes

mod_pof is just a bit of fun, and there are a few issues which you may feel make it impractical for running on a production server:

Apart from that, enjoy.



* But it's worth noting that active fingerprinting of a host would act on the NAT device (eg router, firewall box) if present. Passive fingerprinting reveals the host hidden behind the NAT device.

Linux Services

Books

Code

vBulletin

Fun Stuff

Blog

Pete's Shed




linux support email pete@linuxbox.co.uk
(+44) 07890 592198