Mapping snort alerts

A while back at my day job, my boss was getting ready to meet with all the executives and explain to them why security mattered, what the scope of the threat was, and what we planned to do about it. He knew, from talking to me, that our servers/networks were routinely under attack, and that those attacks were from all over the world. But he wanted a way to make it obvious to non-technical folks.

"Say, will snorby display all the snort alerts on a map?" he asked. "Nope. it's a tool meant for security analysts." But that got me to thinking (always a dangerous thing - grin). Snort just logs to a mysql database, and there is both a mysql and geoIP API in php. So it shouldn't be too hard to make... Lessee, I'd need a map, better make it a mercator projection to make the lat/long to x/y conversion easy since that's the only projection I remember (I was a C/asm programmer in a previous life)...

A few hours later I had several map images for his presentation that were based on real data and suitably eye-opening to the execs, especially when we pointed out this wasn't all the probes/attacks by any means, just the stuff we allowed through the firewall that snort believed to be an attack of some sort. Mission accomplished. :-)

It only took a little extra effort to generalize the PHP scripts a bit and make them more useful even to a security geek like me. Take for instance the following image:

This shows only the snort alerts whose TCP destination port is 22 - ssh probes and brute force attacks. The red dots are source locations scaled to the number of alerts. The blue dots are locations of SGI networks, and since these aren't showing up as sources their scaled to a minimum size.

Now look at this one:

This shows snort alerts to/from two DMZ'd VOIP-related systems. Note that since most VOIP-related snort alerts are from reply traffic coming from the VOIP servers (things like registration failures), this time it's two of the SGI locations that are scaled larger. But what I found interesting was that VOIP attacks have mostly been from US IPs (and a few western european locations). Whereas ssh attacks are generally from everywhere, especially a certain country in Asia which shouldn't surprise anyone who's ever looked at a firewall log. :-)

I even generalized the code so I could feed it different maps and still have it figure out how to convert lat/lon to x/y correctly. For instance,

This alternate map never really makes sense for SGI but it might if, say, our datacenter was in Europe. BTW, the big blue dot over Wisconsin is because of all the failed root logins on an FTP server there.

Since I did most of this for fun in my own time (yeah, I'm a geek, when I'm working in IT I miss programming and when I'm programming I miss IT/Security), I'm making it freely available as-is to anyone else who wants it. I just ask that if you find it useful, please visit next time you're looking to buy server/storage gear. :-) You can download it here.

So, how does all this stuff work? The first bit to look at is mynets.php. This is where I tell the code what subnets are at what lat/lon coordinates and whether or not they're "MINE" (blue dots vs red dots). Yeah, I'm using GeoIP calls to figure out lat/lon coords but let's be honest... These databases are a best guess... at best. For instance, they pretty much always say all of SGI's IP space is in Milpitas which isn't right. They have no idea how the IP space has been subnetted and split up amongst different data centers in Japan, California, Wisconsin, the UK, etc. And what about private IP space like or This file lets me specify stuff like " is in Milpitas, at these specific coords and it's one of our networks".

Let's assume you want to just use one of the 3 maps I included already, for now. These are defined in maps.php. This php script defines a $maps array and at the end, it sets $mapdef (the default map) to be $maps[0] - the one I like best.

Next you'll want to look at mydb.php. This merely opens a connection to a mysql server your barnyard daemons are logging to. It sets the $db variable all the other scripts use.

Ok, now onto the scripts that actually generate a map image. Take a look at dmz-alerts.php. It creates a map object sets a few colors (not really needed - these were for debugging statements), and sets a variable used to store the max number of "hits" a given location was seen. Then it queries mysql for the IPs (source and dest) logged by snort in the last day. For each one found, it creates a location object for the source/dest IPs, and then asks the map object to plot a line between them.

The location objects figure out their lat/lon coords from the IPs given, and whether or not they are one of "my" networks. If the snort alert is both from one of my networks and to one of my networks, the line will be drawn in blue. This is useful for when you're mapping snort alerts going across your VPN or MPLS/WAN networks. I have two different snort databases - one for traffic to/from DMZ segments and one for everything else, and then I also have snort sensors watching WAN traffic, traffic to/from certain datacenter networks, to/from certain user segments, etc.

Anyway, I note the locations and if it's a source I've seen before I note how many times and what the max number of times any single location has been seen (for plotting locations later). Then at the very end, I plot all the locations I've found. Easy-peasy. :-)

One thing to note is that this script doesn't generate HTML - it just dumps a jpeg file. This was because initially all I wanted was an image file. So if you add any debugging print statements, you won't see 'em, and even if the code does work, any print statements will mess up the resulting image. So if you're making changes and debugging, run the php script from the commandline so you can see the raw output and any debugging errors and/or warnings that your webserver might otherwise hide.

Make your own map - This is a little tricky, but here's what you need to do. Get an image and run gimp on it. Identify the x/y location of the "origin" (the upper left corner) as closely as you can. You may need to zoom in. Next, get the width and height of the map in pixels - this is the distance between the origin and the lower right corner, not the width/height of the image itself. For instance on map[0] you'll see the image is 2613 pixels wide, but the width I specify is only 2524 - the horizontal distance between the origin and the right edge of the usable map within the image file.

Next, you need to find the x/y location within the image of lat/lon 0/0. This is the x/y within the image file, not x/y relative to the x/y of the origin.

Once you have all that data, just add the map to the $maps array in maps.php such as:
$mapfile = "mc-amr-302270_comp_1_3.jpg";
$maps[$i++] = new MapDefClass ($mapfile, 42, 22, 2524, 1451, 1934, 1000);

Then you can either set $mapdef to point to your new map at whatever index it's at in the array, or in your individual script, you can ignore the $mapdef variable and use whatever map you want when creating the map object.