Execute script on Snort alert

前端 未结 3 1657
执笔经年
执笔经年 2021-01-03 09:48

I currently am experimenting with a Raspberry Pi. I am running Snort, which is packet detection software. In the case Snort raises an alert, I would want to execute a (Pytho

相关标签:
3条回答
  • 2021-01-03 09:52

    If piping the output delays receiving alerts until snort's stdout buffer is flushed:

    #!/usr/bin/env python
    from __future__ import print_function
    from subprocess import Popen, PIPE, STDOUT
    
    snort_process = Popen(['snort', '-A', 'console', '-c', 'snort.conf'],
                          stdout=PIPE, stderr=STDOUT, bufsize=1,
                          universal_newlines=True, close_fds=True)
    with snort_process.stdout:
        for line in iter(snort_process.stdout.readline, ''):
            #XXX run python script here:
            #    subprocess.call([sys.executable or 'python', '-m', 'your_module'])
            print(line, end='')
    rc = snort_process.wait()
    

    Then you could try a pseudo-tty to enable line-buffereing on snort's side.

    Or run snort -A unsock command and print each alert as soon as it is generated using Unix domain sockets:

    #!/usr/bin/env python
    import ctypes
    import os
    import socket
    from subprocess import Popen
    from snort import Alertpkt
    
    # listen for alerts using unix domain sockets (UDS)
    snort_log_dir = os.getcwd()
    server_address = os.path.join(snort_log_dir, 'snort_alert')
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    try:
        os.remove(server_address)
    except OSError:
        pass
    sock.bind(server_address)
    
    # start snort process
    snort_process = Popen(['snort', '-A', 'unsock', '-l', snort_log_dir,
                           '-c', 'snort.conf'], close_fds=True)
    # receive alerts
    alert = Alertpkt()
    try:
        while 1:
            if sock.recv_into(alert) != ctypes.sizeof(alert):
                break # EOF
            #XXX run python script here `subprocess.call([sys.executable or 'python', '-m', 'your_module'])`
            print("{:03d} {}".format(alert.val, alert.data))
    except KeyboardInterrupt:
        pass
    finally:
        sock.close()
        os.remove(server_address)
        if snort_process.poll() is None: # the process is still running
            snort_process.kill()
            snort_process.wait() # wait for snort process to exit
    

    In your case you could run a script on each alert instead of printing.

    snort.Alertpkt is a ctypes's defition of C struct Alertpkt.

    To try it, you could download the gist that contains a dummy snort script in addition to all the python modules and run run-script-on-alert-unsock.py (or run-script-on-alert-pty.py).

    0 讨论(0)
  • 2021-01-03 10:03

    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

    0 讨论(0)
  • 2021-01-03 10:19

    You could instead log the alerts to a file and then implement something like this, but you would just do a call to your python script instead of notify-send.

    Experimental things in snort can be hard to figure out because there isn't a lot of support out there for them when something goes wrong.

    0 讨论(0)
提交回复
热议问题