HackBlue

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.

11 min read · practice only with authorization

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.

Snippet
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.

Snippet
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 -rn

Is 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.

Internet source scanning your edge

Frequency. Constant, from many sources.

Action. Count it. Alert only if followed by a successful login or exploit attempt.

Fix. Reduce exposed services.

Workstation scanning servers

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?