Getting Started with Zeek for Network Threat Hunting

14 March 2024 | justruss.tech

Zeek (formerly Bro) is a passive network analysis framework. Unlike Snort or Suricata which match signatures against packets, Zeek parses protocols and writes structured logs. The output is far more useful for threat hunting than raw packet
captures because you are querying data, not hunting through hex.

Installation on Ubuntu 22.04

echo "deb http://download.opensuse.org/repositories/security:/zeek/xUbuntu_22.04/ /" \
  | sudo tee /etc/apt/sources.list.d/security:zeek.list
curl -fsSL https://download.opensuse.org/repositories/security:zeek/xUbuntu_22.04/Release.key \
  | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/security_zeek.gpg > /dev/null
sudo apt update && sudo apt install zeek -y

Configure the monitored interface in /etc/zeek/node.cfg:

[zeek]
type=standalone
host=localhost
interface=eth0

Add JSON output to /etc/zeek/local.zeek:

redef LogAscii::use_json = T;
redef LogAscii::json_timestamps = JSON::TS_ISO8601;

Log file locations and what they contain

Logs write to /opt/zeek/logs/current/ by default. The most useful for hunting:

  • conn.log — every connection: src/dst IP, port, protocol, duration, bytes in/out, connection state
  • dns.log — every DNS query: query name, type, response, TTL, answers
  • ssl.log — TLS sessions: JA3/JA3S hashes, SNI, certificate subject, validation status
  • http.log — HTTP requests: method, URI, user-agent, response code, MIME type
  • files.log — file transfers: hash (MD5/SHA1), MIME type, source connection
  • weird.log — protocol anomalies Zeek could not parse cleanly

Hunting beaconing with zeek-cut and awk

Beaconing is regular outbound connections at consistent intervals. Extract connection intervals per destination from conn.log:

cat conn.log | zeek-cut -d ts id.orig_h id.resp_h id.resp_p proto duration \
  | awk -F"\t" "{print $2, $3, $4, $1}" \
  | sort \
  | awk "
    BEGIN {prev_key=\"\"; prev_ts=0}
    {
      key = $1 FS $2 FS $3
      if (key == prev_key && prev_ts > 0) {
        diff = $4 - prev_ts
        print key, diff
      }
      prev_key = key
      prev_ts = $4
    }
  " | sort -k4 -n | head -50

Destinations with many small, consistent interval values are beacon candidates. Standard deviation under 5 seconds across 20+ samples is a strong indicator.

DNS tunnelling detection

DNS tunnelling encodes data in subdomain labels. The query names are long and the label count is high. Extract suspicious queries:

cat dns.log | zeek-cut query qtype_name answers \
  | awk -F"\t" "length($1) > 50 {print}" \
  | sort | uniq -c | sort -rn | head -20

Also useful — query rate per domain, which surfaces tunnelling tools doing fast repeated lookups:

cat dns.log | zeek-cut query \
  | awk -F"." "NF > 3 {print $(NF-1)"."$NF}" \
  | sort | uniq -c | sort -rn | head -20

JA3 hunting in ssl.log

JA3 is an MD5 hash of specific TLS ClientHello fields (version, ciphers, extensions, elliptic curves, elliptic curve formats). Known malware families have published JA3 hashes. A quick check against common ones:

cat ssl.log | zeek-cut ja3 id.orig_h id.resp_h server_name \
  | grep -E "a0e9f5d64349fb13191bc781f81f42e1|51c64c77e60f3980eea90869b68c58a8"

The first hash is a common Cobalt Strike default. The second is associated with Metasploit Meterpreter. Neither is guaranteed — operators change them — but default deployments often do not bother.

Integrating with Elastic

Point Filebeat at the Zeek log directory with the Zeek module enabled:

filebeat modules enable zeek
# Edit /etc/filebeat/modules.d/zeek.yml:
- module: zeek
  connection:
    enabled: true
    var.paths: ["/opt/zeek/logs/current/conn.log"]
  dns:
    enabled: true
    var.paths: ["/opt/zeek/logs/current/dns.log"]
  ssl:
    enabled: true
    var.paths: ["/opt/zeek/logs/current/ssl.log"]