Execute Script on Snort Alert

Execute script on Snort alert

Below are 3 options, hopefully one will work:

  • "Strict" subprocess approach using subprocess's PIPEs
  • Approach using pexpect -- "Pexpect is a pure Python module for spawning child applications; controlling them; and responding to expected patterns in their output." -- not that this is the only non-standard package you would have to grab separately from the default python install.
  • Approach using pseudoterminals and good-old select to read file descriptors

Each of the approaches is bundled in a try_[SOME APPROACH] function. You should be able to update the 3 parameters at the top, then comment/uncomment one approach at the bottom to give it a shot.

It may be worthwhile testing both halves independently. In other words snort + my rpi.py (below). Then, if that works, my timed_printer.py (below) and your python script to toggle the RPi GPIO. If they both work independently, then you can be confident not much will need to be done to get the entire workflow operating properly.

Code

import subprocess

_cmd_lst = ['python', '-u', 'timed_printer.py'] # sudo snort -q -A console -i eth0 -c /etc/snort/snort.conf
_rpi_lst = ['python', '-u', 'rpi.py'] # python script that toggles RPi
_alert = 'TIME' # The keyword you're looking for
# in snort output

#===============================================================================

# Simple helper function that calls the RPi toggle script
def toggle_rpi():
subprocess.call(_rpi_lst)

def try_subprocess(cmd_lst, alert, rpi_lst):
p = subprocess.Popen(' '.join(cmd_lst), shell=True, stdout=subprocess.PIPE, bufsize=1)

try:
while True:
for line in iter(p.stdout.readline, b''):
print("try_subprocess() read: %s" % line.strip())

if alert in line:
print("try_subprocess() found alert: %s" % alert)
toggle_rpi()

except KeyboardInterrupt: print(" Caught Ctrl+C -- killing subprocess...")
except Exception as ex: print ex
finally:
print("Cleaning up...")
p.kill()
print("Goodbye.")

def try_pexpect(cmd_lst, alert, rpi_lst):
import pexpect # http://pexpect.sourceforge.net/pexpect.html

p = pexpect.spawn(' '.join(cmd_lst))

try:
while True:
p.expect(alert) # This blocks until <alert> is found in the output of cmd_str
print("try_pexpect() found alert: %s" % alert)
toggle_rpi()

except KeyboardInterrupt: print(" Caught Ctrl+C -- killing subprocess...")
except Exception as ex: print ex
finally:
print("Cleaning up...")
p.close(force=True)
print("Goodbye.")

def try_pty(cmd_lst, alert, rpi_lst, MAX_READ=2048):
import pty, os, select

mfd, sfd = pty.openpty()

p = subprocess.Popen(' '.join(cmd_lst), shell=True, stdout=sfd, bufsize=1)

try:
while True:
rlist, _, _, = select.select([mfd], [], [])

if rlist:
data = os.read(mfd, MAX_READ)
print("try_pty() read: %s" % data.strip())
if not data:
print("try_pty() got EOF -- exiting")
break
if alert in data:
print("try_pty() found alert: %s" % alert)
toggle_rpi()
elif p.poll() is not None:
print("try_pty() had subprocess end -- exiting")
break

except KeyboardInterrupt: print(" Caught Ctrl+C -- killing subprocess...")
except Exception as ex: print ex
finally:
print("Cleaning up...")
os.close(sfd)
os.close(mfd)
p.kill()
print("Goodbye.")

#===============================================================================

try_subprocess(_cmd_lst, _alert, _rpi_lst)
#try_pexpect(_cmd_lst, _alert, _rpi_lst)
#try_pty(_cmd_lst, _alert, _rpi_lst)

Testing notes

To emulate your snort script (a script that "hangs", then prints something, then goes back to hanging, etc), I wrote this simple python script that I called timed_printer.py:

import time
while True:
print("TIME: %s" % time.time())
time.sleep(5)

And my rpi.py file was simply:

print("TOGGLING OUTPUT PIN")

There's no explicit output flushing here, in an attempt to best emulate normal output.

Final considerations

The first approach will read an entire line at a time. So if you expect your alert to be contained within a single line, you'll be fine.

The second approach (pexpect) will block until alert is encountered.

The third approach will read as soon as data is available, which I should point out is not necessarily a full line. If you see try_pty() read: with fragments of the snort output lines, causing you to miss alerts, you'll need to add some sort of buffering solution.

Docs

  • subprocess
  • pexpect
  • pty, select

References: 1, 2

how can i generate immediate pop-up when a traffic matched with a snort rule

I think you could do something like the following:

#!/bin/sh

#Get current line count
LINES=`wc -l /var/log/snort/alerts | tr -d -c 0-9`

while [ true ]
do
NEWCOUNT=`wc -l /var/log/snort/alerts | tr -d -c 0-9` #Get new line count
if [ $LINES != $NEWCOUNT ]
then
DIFF=`expr $NEWCOUNT - $LINES` #Get the difference
LINES=$NEWCOUNT #Set the line count to the new count
COMMAND="$(tail -n "$DIFF" alert)" #Get the output of the new lines in the file
echo "$(notify-send "$DIFF new alerts: $COMMAND")"
sleep 5 #sleep 5 seconds
fi
done

This will check for new alerts every 5 seconds, if you want to have it check constantly you can remove the sleep, but you may want to use a second or something. I'm no expert in bash, so there may be some cleaning up that you could do with this.
One problem is that if there are multiple new alerts then notify-send will put the alerts on one line, I couldn't find a way around this but you might be able to with some modifications or you can just remove the second part and just have the alert tell you there are new alerts and not even display them.

Snort Rule to Detect Single JS or VBS file in ZIP file

alert tcp any any -> any any (msg:"TEST"; file_data; content:"|0D 0A 0D 0A 50 4B|"; nocase; pcre:"/\x0D\x0A\x0D\x0APK.+?\.js/i"; sid:1000000;)

content:"|0D 0A 0D 0A 50 4B|"; option is matched with ....PK

And i option of PCRE means ignore case sensitive.

Sample Image

And file_data option inspect http response. http://manual-snort-org.s3-website-us-east-1.amazonaws.com/node32.html#SECTION004528000000000000000

Snort rule for wing ftp server authenticated command execution

I would recommend something like the following:

alert tcp any any -> any 5466 / 
(msg:"FTP command execution"; flow:to_server,established; /
content: "POST"; http_method; nocase; /
content:"/admin_lua_script.html"; fast_pattern; http_uri;/
content:"command=os.execute"; http_client_body; nocase; /
metadata: service http;)

Explanation:

dest port 5466:

You should always specify a port when possible. When you have rules that are "any/any" for source/destination snort treats them differently than rules with ports defined.

Important: Since this exploit module runs over port 5466 and is http you NEED to make sure that this port is in your http preprocessor configuration for ports. Specifically, your snort.conf should have a configuration line similar to the following:

preprocessor http_inspect_server: server default profile all ports { 80 ... 5466 ...}

(obviously don't put the dots, just representing other ports you should have in there). If you do not have this port in your preprocessor config for http, all of your http content modifiers will NOT match because snort will not treat traffic on this port as http, which is likely the main issue you're having.

flow:to_server,established;

You only want to check established sessions where the flow is going to the server. This will be more efficient as snort won't have to check random traffic for unestablished sessions and it won't have to check traffic going to the client, since you know the direction for this exploit will always be going to the server. The only way the request would be successful would be if the connection was already established between client and server, if it's not the exploit won't succeed and it's pointless to alert on this.

content: "POST"; http_method; nocase;

You want nocase for the post match because it is not required by http for the method to be all capital letters.

content:"/admin_lua_script.html"; fast_pattern; http_uri;

Adding the fast_pattern option will make the rule more efficient as it will put it into the fast pattern matcher in snort. You know this content is static and never changing (case included) so this is eligible for the fast pattern matcher. Since this is the only content match in the rule that is case sensitive snort would put this into the fast pattern matcher on it's own, but if you modify the rule later on with another content match you would want this to be the content match to use for the fast_pattern matcher.

content:"command=os.execute"; http_client_body; nocase;

This is going to be in the client body, so add the http_client_body option.

metadata: service http;

If you are using target based (which now a days you should be), you need to add the service http keyword. Having this in the rule will no prevent the rule from triggering if you aren't using target based, so it's also a good practice to put this in if you know the service this traffic is.

Additional Note:
Your custom rule sids should be 1000000 or above, anything below this is reserved for the snort distribution rules. See more on that here



Related Topics



Leave a reply



Submit