#!/bin/sh # Copyright (c) 2023 Eric Fahlgren # SPDX-License-Identifier: GPL-2.0 # shellcheck disable=SC2039,SC2155 # "local" not defined in POSIX sh PROG="/usr/bin/snort" MAIN="/usr/share/snort/main.uc" CONF_DIR="/var/snort.d" CONF="${CONF_DIR}/snort_conf.lua" VERBOSE= TESTING= NLINES=0 [ ! -e "$CONF_DIR" ] && mkdir "$CONF_DIR" [ -e /dev/stdin ] && STDIN=/dev/stdin || STDIN=/proc/self/fd/0 [ -e /dev/stdout ] && STDOUT=/dev/stdout || STDOUT=/proc/self/fd/1 [ -t 2 ] && export TTY=1 die() { [ -n "$QUIET" ] || echo "$@" >&2 exit 1 } disable_offload() { # From https://forum.openwrt.org/t/snort-3-nfq-with-ips-mode/161172 # https://blog.snort.org/2016/08/running-snort-on-commodity-hardware.html # Not needed when running the nfq daq as defragmentation is done by the kernel. # What about pcap? local filter_method=$(uci -q get snort.snort.method) if [ "$filter_method" = "afpacket" ]; then local wan=$(uci get snort.snort.interface) if [ -n "$wan" ] && ethtool -k "$wan" | grep -q -E '(tcp-segmentation-offload|receive-offload): on' ; then ethtool -K "$wan" gro off lro off tso off 2> /dev/null log "Disabled gro, lro and tso on '$wan' using ethtool." fi fi } nft_rm_table() { for table_type in 'inet' 'netdev'; do nft list tables | grep -q "${table_type} snort" && nft delete table "${table_type}" snort done } nft_add_table() { if [ "$(uci -q get snort.snort.method)" = "nfq" ]; then print nftables | nft $VERBOSE -f $STDIN [ -n "$VERBOSE" ] && nft list table inet snort fi } setup() { # Generates all the configuration, then reports the config file for snort. # Does NOT generate the rules file, you'll need to do 'update-rules' first. local log_dir=$(uci get snort.snort.log_dir) [ ! -e "$log_dir" ] && mkdir -p "$log_dir" nft_rm_table print snort > "$CONF" nft_add_table echo "$CONF" } teardown() { # Merely cleans up after. nft_rm_table [ -e "$CONF" ] && rm "$CONF" } update_rules() { /usr/bin/snort-rules $TESTING } print() { # '$1' is file type to generate, one of: # config, snort or nftables TYPE=$1 utpl -S "$MAIN" } check() { local manual=$(uci get snort.snort.manual) [ "$manual" = 1 ] && return 0 [ -n "$QUIET" ] && OUT=/dev/null || OUT=$STDOUT local warn no_rules if [ -n "$VERBOSE" ]; then warn='--warn-all' no_rules=0 else warn='-q' no_rules=1 fi local test_conf="${CONF_DIR}/test_conf.lua" _SNORT_WITHOUT_RULES="$no_rules" print snort > "${test_conf}" || die "Errors during generation of snort config." if $PROG -T $warn -c "${test_conf}" 2> $OUT ; then rm "${test_conf}" else die "Errors in snort config tests. Examine ${test_conf} for issues." fi if [ "$(uci -q get snort.snort.method)" = "nfq" ]; then local test_nft="${CONF_DIR}/test_conf.nft" print nftables > "${test_nft}" || die "Errors during generation of nftables config." if nft $VERBOSE --check -f "${test_nft}" ; then rm "${test_nft}" else die "Errors in nftables config tests. Examine ${test_nft} for issues." fi fi } report() { # Reported IPs have source port stripped, but destination port (if any) # retained. # # json notes # from alert_fast: # 08/30-11:39:57.639021 [**] [1:382:11] "PROTOCOL-ICMP PING Windows" [**] [Classification: Misc activity] [Priority: 3] {ICMP} 10.1.1.186 -> 10.1.1.20 # # same event in alert_json (single line broken for clarity): # { "timestamp" : "08/30-11:39:57.639021", "pkt_num" : 5366, "proto" : "ICMP", "pkt_gen" : "raw", # "pkt_len" : 60, "dir" : "C2S", "src_ap" : "10.1.1.186:0", "dst_ap" : "10.1.1.20:0", # "rule" : "1:382:11", "action" : "allow" } # # Second part of "rule", 382, is "sid" in ruleset, suffixing 11 is "rev". # grep '\bsid:382\b' /etc/snort/rules/snort.rules (again, single line broken for clarity): # alert icmp $EXTERNAL_NET any -> $HOME_NET any ( msg:"PROTOCOL-ICMP PING Windows"; # itype:8; content:"abcdefghijklmnop",depth 16; metadata:ruleset community; # classtype:misc-activity; sid:382; rev:11; ) # # Not sure where the prefixing 1 comes from. local logging=$(uci get snort.snort.logging) local log_dir=$(uci get snort.snort.log_dir) local pattern="$1" if [ "$logging" = 0 ]; then die "Logging is not enabled in snort config." fi [ "$NLINES" = 0 ] && output="cat" || output="head -n $NLINES" local msg src dst dir tmp="/tmp/snort.report.$$" for file in "${log_dir}"/*alert_json.txt; do while read -r line; do eval $(jsonfilter -s "$line" -e 'msg=$.msg' -e 'src=$.src_ap' -e 'dst=$.dst_ap' -e 'dir=$.dir') src=$(echo "$src" | sed 's/:.*$//') # Delete all source ports. dst=$(echo "$dst" | sed 's/:0$//') # Delete unspecified dest port. echo "$msg#$src#$dst#$dir" done < "$file" done | grep -i "$pattern" > "$tmp" echo "Events involving ${pattern:-all IPs}" n_incidents="$(wc -l < $tmp)" lines=$(sort "$tmp" | uniq -c | sort -nr \ | awk -F'#' '{printf "%-80s %s %-13s -> %s\n", $1, $4, $2, $3}') echo "$lines" | $output n_lines=$(echo "$lines" | wc -l) [ "$NLINES" -gt 0 ] && [ "$NLINES" -lt "$n_lines" ] && echo " ... Only showing $NLINES of $n_lines most frequent incidents." printf "%7d total incidents\n" "$n_incidents" rm "$tmp" } status() { echo -n 'snort is ' ; service snort status ps w | grep -E 'PID|snort' | grep -v grep } while [ -n "$1" ]; do case "$1" in -q) export QUIET=1 shift ;; -v) export VERBOSE=-e shift ;; -t) TESTING=-t shift ;; -n) NLINES="$2" shift shift ;; *) break ;; esac done case "$1" in setup) setup ;; teardown) teardown ;; resetup) QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting. Run 'snort-mgr check'" teardown setup ;; update-rules) update_rules ;; check) check ;; print) print "$2" ;; report) report "$2" ;; status) status ;; *) cat < snort-mgr -t update-rules > /etc/init.d/snort start > ping -c4 8.8.8.8 > logread -e "TEST ALERT" $0 print config|snort|nftables Print the rendered file contents. config = Display contents of /etc/config/snort, but with all values and descriptions. Missing values shown with defaults. snort = The snort configuration file, which is a lua script. nftables = The nftables script used to define the input queues when using the 'nfq' DAQ. help = Display config file help. $0 [-q] check Test the rendered config using snort's check mode without applying it to the running system. $0 status Print the nfq counter values and blah blah blah USAGE ;; esac