Thursday, October 29, 2009

Snort alert_unixsock

In my last post, I talked about a program and named pipe I put together so that I could get email alerts when snort detected certain events. I am a little surprised that this functionality isn't built into the software already and I'm a little surprised that there isn't a built in way to send alert output to a program. In other words, not having to have a process running all the time watching a named pipe, but that gets run by snort when certain alerts fire.

Anyway, I wanted to talk about a failure I had with snort because I hope that someone will read this and either learn from my troubleshooting process or provide me with some info on what I did wrong. I want to talk about sending alerts to a unix socket.

When you run snort, you specify a log directory that you want snort to send logs to using the -l option at the command line. When you turn on alert_unixsock snort will send alerts to a socket called snort_alert in your log directory. You can turn on sockets by adding the line "output alert_unixsock" to your snort.conf file. I used it within a custom ruletype.

But before you do that, you need to create the socket. Snort will not do it for you. So using my favorite programming language, I whipped up something to create the socket, listen for connections, and print anything that it receives. I borrowed heavily from the python.org web site for this code.

#! /usr/bin/env python
import os
import socket

s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

try:
os.remove("/home/kevin/snort/logs/snort_alert")
except OSError:
pass

s.bind("/home/kevin/snort/logs/snort_alert")
s.listen(3)
while True:
conn, addr = s.accept()
datain = conn.recv(1024)
print(datain)
conn.close()


So I fired up this script which creates the listener, and then I fired up snort. I wrote up a rule that would fire almost constantly and I waited for something to be written to my screen by the socket reader. But I got nothing. Flabbergasted, I did a "tail -f /home/kevin/snort/logs/alert" and watched my constantly running alert fire off constantly. So I know that the rule I wrote is good, and I know that snort is doing something.

So the next logical thing to check was the program itself. I couldn't find anything that would act as a socket sniffer, which is what I would have liked to have done. So instead, I wrote up this script that would connect to the socket and send some data over.

#! /usr/bin/env python

import socket

s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect('/home/kevin/snort/logs/snort_alert')

s.send('Test message.')
s.close()


With my listener running, I fired up my sending program a couple times and sure enouh, my listener wrote "Test message" to standard output every time. So I know that my listener is good. It seems like snort isn't sending data to the socket. I checked, and double-checked, and triple-checked the syntax of the command in snort.conf, but there really isn't anything to it. I even delved into the murky world of strace to see what was going on.
access("/home/kevin/snort/logs/snort_alert", W_OK) = 0


Unless I'm mistaken, the zero at the end means that snort was able to write to the socket, in other words there were no permission problems. So I'm really at a loss. Does anyone have any ideas as to what I might be missing?

3 comments:

Anonymous said...

I don't know if you still care about this, but the main problem is that you are using the wrong kind of sock (STREAM, whereas Snort uses DGRAM). I had to look at the Snort code to figure this out - its not documented at all afaik. Here is some code that works for me (with Snort v 2.8.4.1) - sorry about ugly formatting.

#! /usr/bin/env python
import os
import socket
import struct

# from src/decode.h
ALERTMSG_LENGTH=256
SNAPLEN=1500

def main():
s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

# This format does NOT include the 'Event' struct which is the last element
# of the _AlertPkt struct in src/output-plugins/spo_alert_unixsock.h
# Thus, we must truncate the messages ('datain[:fmt_size]') before passing
# them to struct.unpacket()
fmt = "%ds9I%ds" % (ALERTMSG_LENGTH, SNAPLEN)
fmt_size = struct.calcsize(fmt)

try:
os.remove("logs/snort_alert")
except OSError:
pass

s.bind("logs/snort_alert")
while True:
try:
(datain, addr) = s.recvfrom(4096)
(msg, ts_sec, ts_usec, caplen, pktlen, dlthdr, nethdr, transhdr, data, val, pkt) = \
struct.unpack(fmt, datain[:fmt_size])

print msg
# optionally, do something with the pcap pkthdr (ts_sec + ts_usec +
# caplen + pktlen) and packet body (pkt)
except struct.error, e:
print "bad message? (msglen=%d): %s" % (len(datain), e.message)

if __name__ == '__main__':
main()

Unknown said...

@anonymous
Holy Crap! Thanks! I can't wait to try out your code. I wish the documentation was better and a person didn't have to go through all the trouble you did to find the answer. Thanks for sharing.

Ian Rose said...

My pleasure! I was trying to figure out the exact same thing you were (which is how I came upon your posting via google) - would be nice if Snort had an easier way to pipe alerts without all these hijinx, but oh well.