2023-11-27 16:21:43 +00:00
|
|
|
#!/bin/sh
|
|
|
|
# Copyright (c) 2023 Eric Fahlgren <eric.fahlgren@gmail.com>
|
|
|
|
# SPDX-License-Identifier: GPL-2.0
|
2023-12-06 23:37:32 +00:00
|
|
|
# shellcheck disable=SC2039,SC2155 # "local" not defined in POSIX sh
|
2023-11-27 16:21:43 +00:00
|
|
|
|
|
|
|
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
|
2023-12-06 23:37:32 +00:00
|
|
|
# Not needed when running the nfq daq as defragmentation is done by the kernel.
|
2023-11-27 16:21:43 +00:00
|
|
|
# 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.
|
2023-12-06 23:37:32 +00:00
|
|
|
local log_dir=$(uci get snort.snort.log_dir)
|
|
|
|
[ ! -e "$log_dir" ] && mkdir -p "$log_dir"
|
2023-11-27 16:21:43 +00:00
|
|
|
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
|
2023-12-06 23:37:32 +00:00
|
|
|
local warn no_rules
|
|
|
|
if [ -n "$VERBOSE" ]; then
|
|
|
|
warn='--warn-all'
|
|
|
|
no_rules=0
|
|
|
|
else
|
|
|
|
warn='-q'
|
|
|
|
no_rules=1
|
|
|
|
fi
|
|
|
|
|
2023-11-27 16:21:43 +00:00
|
|
|
local test_conf="${CONF_DIR}/test_conf.lua"
|
2023-12-06 23:37:32 +00:00
|
|
|
_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
|
2023-11-27 16:21:43 +00:00
|
|
|
rm "${test_conf}"
|
2023-12-06 23:37:32 +00:00
|
|
|
else
|
|
|
|
die "Errors in snort config tests. Examine ${test_conf} for issues."
|
2023-11-27 16:21:43 +00:00
|
|
|
fi
|
2023-12-06 23:37:32 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2023-11-27 16:21:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
2023-12-06 23:37:32 +00:00
|
|
|
local msg src dst dir
|
2023-11-27 16:21:43 +00:00
|
|
|
tmp="/tmp/snort.report.$$"
|
2023-12-06 23:37:32 +00:00
|
|
|
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}"
|
2023-11-27 16:21:43 +00:00
|
|
|
n_incidents="$(wc -l < $tmp)"
|
|
|
|
lines=$(sort "$tmp" | uniq -c | sort -nr \
|
2023-12-06 23:37:32 +00:00
|
|
|
| awk -F'#' '{printf "%-80s %s %-13s -> %s\n", $1, $4, $2, $3}')
|
2023-11-27 16:21:43 +00:00
|
|
|
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() {
|
2023-12-06 23:37:32 +00:00
|
|
|
echo -n 'snort is ' ; service snort status
|
|
|
|
ps w | grep -E 'PID|snort' | grep -v grep
|
2023-11-27 16:21:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2023-12-06 23:37:32 +00:00
|
|
|
QUIET=1 check || die "The generated snort lua configuration contains errors, not restarting. Run 'snort-mgr check'"
|
2023-11-27 16:21:43 +00:00
|
|
|
teardown
|
|
|
|
setup
|
|
|
|
;;
|
|
|
|
update-rules)
|
|
|
|
update_rules
|
|
|
|
;;
|
|
|
|
check)
|
|
|
|
check
|
|
|
|
;;
|
|
|
|
print)
|
|
|
|
print "$2"
|
|
|
|
;;
|
|
|
|
report)
|
|
|
|
report "$2"
|
|
|
|
;;
|
|
|
|
status)
|
|
|
|
status
|
|
|
|
;;
|
|
|
|
*)
|
|
|
|
cat <<USAGE
|
|
|
|
Usage:
|
|
|
|
|
|
|
|
-n = show only NLINES of output
|
|
|
|
-q = quiet
|
|
|
|
-v = verbose
|
|
|
|
-t = testing mode
|
|
|
|
|
|
|
|
$0 [-v] [-q] setup|teardown|resetup
|
|
|
|
|
|
|
|
Normally only used internally by init scripts to manage the generation
|
|
|
|
of configuration files and any needed firewall rules. None of these
|
|
|
|
modify the snort rules in any way (see 'update-rules').
|
|
|
|
setup = generates snort config, sets up firewall.
|
|
|
|
teardown = removes any firewall rules.
|
|
|
|
resetup = shorthand for teardown and then setup.
|
|
|
|
|
|
|
|
|
|
|
|
$0 [-n lines] report [pattern]
|
|
|
|
|
|
|
|
Report on incidents. Note this is somewhat experimental, so suggested
|
|
|
|
improvements are quite welcome.
|
2023-12-06 23:37:32 +00:00
|
|
|
pattern = A case-insensitive grep pattern used to filter output.
|
2023-11-27 16:21:43 +00:00
|
|
|
|
|
|
|
$0 [-t] update-rules
|
|
|
|
|
|
|
|
Download and install the snort ruleset. Testing mode generates a canned
|
|
|
|
rule that matches IPv4 ping requests. A typical test scenario might look
|
|
|
|
like:
|
|
|
|
|
|
|
|
> 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.
|
2023-12-06 23:37:32 +00:00
|
|
|
help = Display config file help.
|
2023-11-27 16:21:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
$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
|