Zeek conn.log¶
In one sentence
One row per connection (TCP, UDP, ICMP) with who/what/when/how-much and a compact record of how the TCP handshake went — the log you aggregate first, and the uid hub every other Zeek log hangs off.
Fields¶
| Field | Type | Meaning | Analyst notes |
|---|---|---|---|
ts |
time | Time of the first packet | UTC epoch. Convert with zeek-cut -d |
uid |
string | Unique connection ID | Join key to every other log |
id.orig_h / id.orig_p |
addr / port | Originator (client) IP / port | Originator = who sent the first packet |
id.resp_h / id.resp_p |
addr / port | Responder (server) IP / port | Sort/count by this to find C2 |
proto |
enum | tcp, udp, icmp |
|
service |
string | Protocol Zeek detected by content (http, ssl, dns, smb, ssh, rdp, krb_tcp, ntlm, dce_rpc…) |
- = unknown/undetected. Multiple: ssl,http. Mismatch with port = interesting |
duration |
interval | Seconds from first to last packet | Very long + low bytes = interactive/keep-alive |
orig_bytes / resp_bytes |
count | Payload bytes from originator / responder (TCP: from sequence numbers, so retransmits don't inflate) | Asymmetry tells the direction of data flow |
conn_state |
string | Connection outcome summary (table below) | Scans: S0, REJ; normal: SF |
local_orig / local_resp |
bool | Is orig/resp in Site::local_nets? |
Only set if you configured local nets |
missed_bytes |
count | Bytes lost due to packet drops | High = capture problems, distrust byte counts |
history |
string | Per-packet flag history (below) | Reads like a story of the handshake |
orig_pkts / orig_ip_bytes |
count | Packets / IP-level bytes from originator (includes headers) | Compare to orig_bytes |
resp_pkts / resp_ip_bytes |
count | Same for responder | |
tunnel_parents |
set | uids of encapsulating tunnels (GRE, Teredo, VXLAN, AYIYA) |
Non-empty = tunnelled traffic |
community_id (if loaded) |
string | Hash of the 5-tuple shared with Suricata/Arkime/Elastic | Pivot between tools |
orig_l2_addr / resp_l2_addr (optional policy) |
string | MAC addresses | Ties IP → device on local segment |
vlan, inner_vlan (optional) |
int | 802.1Q tags |
conn_state values¶
| State | Meaning | What it usually is |
|---|---|---|
S0 |
SYN seen, no reply | Port scan, firewall drop, dead host |
S1 |
Handshake done, not terminated | Still open at end of capture / long session |
SF |
Normal establish & teardown | Regular connection ("Success Finished") |
REJ |
SYN → RST | Closed port (scan hit a closed port) |
S2 |
Established, orig sent FIN, no FIN from resp | |
S3 |
Established, resp sent FIN, no FIN from orig | |
RSTO |
Established, originator aborted with RST | Client killed it |
RSTR |
Established, responder aborted with RST | Server killed it (IDS/firewall reset, service crashed) |
RSTOS0 |
Orig sent SYN then RST; no SYN-ACK from resp | Scanner sending RST after no reply |
RSTRH |
Resp sent SYN-ACK then RST; no SYN from orig seen | Asymmetric capture |
SH |
Orig SYN then FIN, no SYN-ACK | "Half-open" — often scans/evasion |
SHR |
Resp SYN-ACK then FIN, no SYN from orig | Asymmetric capture |
OTH |
No SYN seen, mid-stream | Capture started mid-connection, or UDP/ICMP |
history letters¶
Uppercase = originator, lowercase = responder. Order = order seen (repeats are collapsed on later versions with ^ marking direction flips).
| Letter | Meaning |
|---|---|
S / s |
SYN (no ACK) |
H / h |
SYN-ACK ("handshake") |
A / a |
Pure ACK |
D / d |
Packet with payload ("data") |
F / f |
FIN |
R / r |
RST |
C / c |
Bad checksum |
G / g |
Content gap |
T / t |
Retransmission |
W / w |
Zero window |
I / i |
Inconsistent packet (SYN+RST) |
Q / q |
Multi-flag packet (SYN+FIN) |
^ |
Direction flipped (Zeek guessed orig/resp wrongly and fixed it) |
Examples: ShADadFf = perfect normal connection. S = single SYN, nothing back (scan / dropped). Sr = SYN, RST back (closed port). ShAD then nothing = client sent data, got nothing, capture ended or hung. ShADadR = normal until server reset. ^d = only saw responder data, mid-stream.
Quick queries¶
# Top talkers by destination (who is everyone connecting to?)
zeek-cut id.resp_h id.resp_p < conn.log | sort | uniq -c | sort -rn | head -20
# Outbound connections from one host, readable time, longest first
zeek-cut -d ts id.orig_h id.resp_h id.resp_p service duration orig_bytes resp_bytes conn_state < conn.log \
| awk '$2=="10.0.0.25"' | sort -k6 -rn | head
# Port scan detection: many S0/REJ from one source to many ports
zeek-cut id.orig_h id.resp_h id.resp_p conn_state < conn.log \
| awk '$4=="S0"||$4=="REJ"' | awk '{print $1, $2}' | sort | uniq -c | sort -rn | head
# Services that don't match their port (443 that isn't ssl, 53 that isn't dns, 80 that isn't http)
zeek-cut id.resp_p service < conn.log | awk '($1==443 && $2!="ssl" && $2!="-") || ($1==53 && $2!="dns") || ($1==80 && $2!="http")' | sort | uniq -c | sort -rn
# Total bytes uploaded per internal host to external hosts (exfil hunt)
zeek-cut id.orig_h id.resp_h orig_bytes < conn.log | awk '$1 ~ /^10\./ && $2 !~ /^10\./ {s[$1" -> "$2]+=$3} END {for (k in s) print s[k], k}' | sort -rn | head
# Long-lived connections (> 1 hour)
zeek-cut -d ts uid id.orig_h id.resp_h id.resp_p duration < conn.log | awk '$6>3600' | sort -k6 -rn
index=zeek sourcetype=zeek:conn
| stats count sum(orig_bytes) as up sum(resp_bytes) as down avg(duration) as avg_dur by id.orig_h, id.resp_h, id.resp_p, service
| eval up_MB=round(up/1024/1024,1), down_MB=round(down/1024/1024,1)
| sort - up_MB
stats … by groups rows by the fields after by and computes one summary row per group; sum(orig_bytes) as up adds up the upload bytes and names the result up. eval creates new fields from a formula (here converting bytes to MB and rounding to 1 decimal). sort - up_MB puts the biggest uploader on top.
index=zeek sourcetype=zeek:conn conn_state IN (S0, REJ)
| stats dc(id.resp_p) as ports dc(id.resp_h) as hosts by id.orig_h
| where ports > 50 OR hosts > 50
IN (…) matches any listed value. dc() = distinct count — how many different ports/hosts one source touched; where keeps only rows over the threshold. This is a port-scan (ports) and host-sweep (hosts) detector.
Analysis tips¶
Pivot pattern
Aggregate in conn.log → find the odd pair (id.orig_h, id.resp_h, id.resp_p) → grab its uids → grep <uid> *.log → read the protocol detail. grep CHhAvVGS1DHFjwGM9 http.log ssl.log dns.log files.log.
- Byte counts are payload only. A 0-byte
SFconnection is a handshake with no data — TCP probe, health check, or a beacon that got nothing to do. orig_bytesvsorig_ip_bytes: iforig_bytesis 0 butorig_ip_bytesis large, the payload was in retransmits or Zeek couldn't reassemble (missed_bytes) — capture quality issue.- UDP "connections" are flows grouped by 5-tuple with an inactivity timeout;
durationcan span many datagrams. DNS to the same resolver may show as one long UDP conn. - ICMP:
id.orig_p= ICMP type,id.resp_p= code. A lot of type 8/0 with largeorig_bytes= ICMP tunnel. - Local vs. external: set
Site::local_netsinlocal.zeeksolocal_orig/local_resppopulate — then "internal → external with bigorig_bytes" is a one-liner. - Beaconing lives here: same
id.orig_h/id.resp_h/id.resp_p, near-constant interval betweentsvalues, similarorig_bytes. See Beaconing & C2. - Lateral movement looks like internal → internal
445(smb),135+ high port (dce_rpc),3389(rdp),5985(httpon that port = WinRM),22. Count internal-to-internal pairs that never occurred before the incident window. - Zeek may flip orig/resp when it starts mid-stream (
^in history,OTHstate) — sanity check port numbers when the "server" is on port 51234.
Correlation¶
| Question | Then look at |
|---|---|
| What was actually said? | uid → http.log, ssl.log, dns.log, smb_*.log, ssh.log, rdp.log |
| Which file went over it? | uid → files.log (conn_uids) → fuid → pe.log, extract_files/ |
| Which process on the host made it? | Sysmon 3 (SourceIp/SourcePort/DestinationIp + time), Windows 5156, SRUM hourly bytes per app |
| Which user? | ntlm.log / kerberos.log (username, client), Windows 4624 on the destination |
| Was it alerted on? | notice.log, Suricata eve.json (community_id / 5-tuple + time) |
| Packets | tshark -r cap.pcap -Y "ip.addr==A && tcp.port==P" |