NetFlow Analysis
NetFlow records connection metadata — who talked to whom, for how long, and how many bytes — without storing the full packet payload. Most enterprise networks have NetFlow available for the past 30-90 days where full PCAPs would be terabytes. NetFlow is often the only network evidence available for older compromise dates.
The investigation reveals the attacker had been in the environment for 47 days before detection. You only have full PCAPs for the last 72 hours. But NetFlow data goes back 90 days. NetFlow analysis shows: a consistent beaconing pattern from FINANCE-SRV01 to 185.220.101.47:443 starting 47 days ago (the initial compromise date). It also shows three large outbound transfers (3.2 GB, 1.8 GB, and 0.7 GB) to a different external IP in the last 30 days — three exfiltration events, with approximate data volumes. Without full PCAPs, this is the only network evidence of the exfiltration.
NetFlow Fundamentals
NetFlow Record Contents
═══════════════════════════════════════════════════════════════════
A NetFlow record = one bidirectional conversation (one "flow")
Fields in a typical NetFlow v5/v9/IPFIX record:
srcaddr Source IP address
dstaddr Destination IP address
srcport Source port
dstport Destination port
prot IP protocol (6=TCP, 17=UDP, 1=ICMP)
bytes Total bytes in this flow direction
packets Total packets
first Start time of flow
last End time of flow (= first + duration)
tcp_flags TCP flags seen in this flow (SYN, FIN, RST, ACK)
What NetFlow CAN show:
✓ Who talked to whom (all connections)
✓ How much data was transferred (bytes per flow)
✓ When connections occurred (timestamps)
✓ How long sessions lasted
✓ Port scanning (many flows to different ports from same source)
✓ Beaconing (regular interval, low-byte flows)
✓ Exfiltration (large outbound data volumes)
What NetFlow CANNOT show:
✗ Packet payload (no content, only metadata)
✗ URLs or domain names (only IPs and ports)
✗ Authentication details
✗ Application-layer context
NetFlow Analysis with nfdump
nfdump is the standard command-line tool for analyzing NetFlow/IPFIX records on Linux:
FLOWS="/cases/CASE-2026-009/netflow/"
SUSPECT="185.220.101.47"
COMPROMISED="10.10.5.20" # FINANCE-SRV01
# Step 1: Overview of traffic involving the compromised host
nfdump -R $FLOWS -n 30 -s record/bytes \
-A dstaddr \
"src ip $COMPROMISED" | head -30
# Shows top destinations by bytes from the compromised host
# Step 2: Find outbound traffic to external IPs (exfiltration hunting)
nfdump -R $FLOWS -o "fmt:%ts %td %sa %da %dp %byt %fl" \
"src ip $COMPROMISED and not ip net 10.0.0.0/8 and not ip net 172.16.0.0/12 and not ip net 192.168.0.0/16" | \
sort -k8 -rn | head -20
# Sort by bytes — largest exfiltration first
# Step 3: Beacon detection — find regular small connections
nfdump -R $FLOWS -o "fmt:%ts %sa %da %dp %byt" \
"src ip $COMPROMISED and dst ip $SUSPECT" | \
awk '{print $1}' | \ # extract timestamps only
python3 -c "
import sys, math
times = [float(line.split()[0]) for line in sys.stdin if line.strip()]
if len(times) < 2: exit()
intervals = [times[i+1]-times[i] for i in range(len(times)-1)]
avg = sum(intervals)/len(intervals)
stdev = math.sqrt(sum((x-avg)**2 for x in intervals)/len(intervals))
print(f'Flows to {SUSPECT}: {len(times)}')
print(f'Avg interval: {avg:.1f}s, StdDev: {stdev:.1f}s, CV: {stdev/avg:.3f}')
"
# Step 4: Date range query — how long has this C2 been active?
nfdump -R $FLOWS \
-t 2026-08-01/2026-09-20 \
"src ip $COMPROMISED and dst ip $SUSPECT" | \
head -5 # First flow = first C2 communication
# Step 5: Large transfers to specific external IP (exfiltration)
nfdump -R $FLOWS -n 10 -s record/bytes \
"src ip $COMPROMISED and dst not ip 10.0.0.0/8 and bytes > 5000000" | head -20
Python-Based Flow Analysis
#!/usr/bin/env python3
"""
Analyze NetFlow CSV export for exfiltration and C2 patterns.
Export from nfdump: nfdump -R flows/ -o csv > flows.csv
"""
import csv
import sys
from collections import defaultdict
from datetime import datetime
FLOWS_CSV = "/cases/CASE-2026-009/netflow/flows.csv"
INTERNAL_NETS = ["10.", "172.16.", "172.17.", "192.168."]
def is_internal(ip):
return any(ip.startswith(p) for p in INTERNAL_NETS)
# Load flows
flows = []
with open(FLOWS_CSV) as f:
reader = csv.DictReader(f)
for row in reader:
flows.append(row)
print(f"Total flows: {len(flows)}")
# Find large outbound flows (exfiltration candidates)
print("\n=== TOP OUTBOUND TRANSFERS (EXFIL CANDIDATES) ===")
outbound = [
f for f in flows
if is_internal(f.get('srcaddr', '')) and not is_internal(f.get('dstaddr', ''))
]
outbound.sort(key=lambda x: int(x.get('bytes', 0)), reverse=True)
for f in outbound[:10]:
gb = int(f.get('bytes', 0)) / 1e9
print(f" {f['first'][:19]} {f['srcaddr']} → {f['dstaddr']}:{f['dstport']}"
f" {gb:.2f} GB dur:{f.get('td','?')}s")
# Beacon detection — group flows by src/dst pair and analyze timing
print("\n=== BEACON CANDIDATES (REGULAR INTERVAL FLOWS) ===")
pair_times = defaultdict(list)
for f in flows:
if is_internal(f.get('srcaddr', '')) and not is_internal(f.get('dstaddr', '')):
key = (f['srcaddr'], f['dstaddr'], f['dstport'])
try:
ts = float(f['first'])
pair_times[key].append(ts)
except:
pass
import math
for (src, dst, dport), times in sorted(pair_times.items(), key=lambda x: -len(x[1])):
if len(times) < 10:
continue
times.sort()
intervals = [times[i+1]-times[i] for i in range(len(times)-1)]
avg = sum(intervals)/len(intervals)
stdev = math.sqrt(sum((x-avg)**2 for x in intervals)/len(intervals))
cv = stdev / avg if avg > 0 else 99
if cv < 0.25 and 30 < avg < 3600:
print(f" {src} → {dst}:{dport} count:{len(times)} "
f"avg:{avg:.0f}s CV:{cv:.3f} *** BEACON LIKELY ***")
Q & A
Q: NetFlow shows a 3 GB outbound transfer but you don't know what data it was. How do you determine what was exfiltrated?
NetFlow gives you the "when, where, and how much" — not the "what." To determine what was exfiltrated, triangulate from other artifacts: (1) SRUM database: SRUM records per-application network usage. If the 3 GB transfer was via explorer.exe or a specific application, SRUM shows that. (2) File system timeline: look at what large files existed or were recently accessed/modified in the same time window as the NetFlow transfer. Staging archives (large .zip/.7z files created near the transfer time) are the staged exfiltration data. (3) Recycle Bin / deleted files: attackers often delete the staging archive after exfiltration; recovering it confirms the content. (4) Browser or application history: if the transfer used a cloud upload (browser-based), browser history and browser artifacts show which URL was accessed. (5) Email artifacts: if exfiltration was via email, exchange logs or email client artifacts show attachments sent. (6) If PCAP is available at any point: even a partial PCAP overlapping the transfer timestamps can reconstruct the file names from HTTP headers even if the content is compressed.