Tools · Nmap and network discovery · 22 / 25
How to detect a port scan in firewall and Zeek logs
Recognize scan patterns in iptables logs and Zeek conn.log, count distinct ports per source, and decide whether a scan is noise or a lead.
What a scan looks like in a log
A port scan has a simple shape: one source address touching many destination ports, or many destination hosts, in a short time, with most attempts failing. In a firewall log that shows up as a burst of drop entries with the same source and rising port numbers. In Zeek’s conn.log, SYN scans produce connections with conn_state S0, meaning a SYN was seen with no reply, or REJ, meaning the target answered with a RST.
Horizontal scans hit one port across many hosts, like 445 across a subnet. Vertical scans hit many ports on one host. Both are worth knowing because they suggest different intentions.
Logging dropped packets on Linux
iptables and nftables do not log drops by default. Add a LOG rule with a prefix and a rate limit just before the final drop so the log does not flood. Entries land in the kernel log, readable with journalctl -k. Each line includes SRC, DST, PROTO, SPT, and DPT.
Count distinct destination ports per source over a window to separate scans from ordinary blocked traffic.
sudo iptables -A INPUT -m limit --limit 20/min -j LOG --log-prefix "FW-DROP " sudo iptables -A INPUT -j DROP journalctl -k --since "10 min ago" | grep FW-DROP | \ grep -oP "SRC=\S+|DPT=\d+" | paste - - | sort -u | cut -f1 | sort | uniq -c | sort -rn | head
Lab: scan yourself and find it
Run a SYN scan of the top 1,000 ports from your scanner VM against a lab host running Zeek or a logging firewall. Then query Zeek’s conn.log for sources with many distinct destination ports in S0 or REJ state. Your scanner’s address should top the list with roughly 1,000 ports.
Run a second, slower scan with -T2 and see how the count per minute drops. That is why detection windows should be hours, not seconds, if you want to catch patient scans.
cat conn.log | zeek-cut id.orig_h id.resp_p conn_state | \
awk '$3=="S0"||$3=="REJ"{print $1, $2}' | sort -u | \
awk '{c[$1]++} END{for (s in c) if (c[s]>50) print c[s], s}' | sort -rnIs it worth an alert?
Internet-facing addresses are scanned constantly by research projects, search engines for devices, and automated attackers. Alerting on every external scan creates noise. Track it as a metric, and alert when a scan is followed by a successful connection to a sensitive service from the same source.
Internal scans are different. A workstation sweeping port 445 across the server network is unusual and may indicate malware spreading or an unauthorized tool. That deserves investigation every time, after checking whether a vulnerability scanner was scheduled.
Diagram
External noise versus internal lead
The same pattern means different things depending on the source.
Frequency. Constant, from many sources.
Action. Count it. Alert only if followed by a successful login or exploit attempt.
Fix. Reduce exposed services.
Frequency. Rare.
Action. Investigate the host and user now.
Check first. Was a sanctioned scanner scheduled?
Check yourself
1. In Zeek conn.log, what does conn_state S0 mean?
2. Which metric best separates a scan from ordinary blocked traffic?
3. Which scan deserves immediate investigation?