top of page

What I Found In A Zeek Dataset (And How I Found It)

  • Jun 5
  • 9 min read

Updated: Jul 11

What happens when a data analyst opens a Zeek conn.log for the first time — and starts asking questions?


Updated: After the deeper analysis in Part 2, I revisited the host classifications and the kill-chain reconstruction in this post and tightened them to what the data strictly supports. The core discovery — a Mirai-infected host mass-scanning from inside the network — is unchanged and fully reproducible. What I corrected: two hosts I'd grouped as "infected" turned out to be doing different things (internal reconnaissance, ordinary HTTP), two IPs I'd flagged as command-and-control don't fit that role, and a fixed "heartbeat" packet size I cited didn't hold up on re-analysis. Re-auditing your own findings is the job — so I'm leaving the correction visible rather than quietly editing. The real command-and-control finding lives in Part 2.


What is Zeek — and Why Should Analysts Care?


Zeek (formerly Bro) is an open-source network analysis framework widely used in Security Operations Centers (SOCs). Unlike a firewall that blocks traffic, Zeek passively monitors network activity and produces detailed, structured logs — one for Domain Name System (DNS) queries, one for Hypertext Transfer Protocol (HTTP) activity, and one for connection metadata. The logs are a catalog of every network connection.


The dataset I used comes from Kaggle: DNS and Connections Log — Zeek/Corelight. It includes two log types: dns.log and conn.log. I focused on conn.log — the connection log.


💡 What is conn.log?

The connection log captures every network session Zeek observes — who talked to whom, for how long, how much data moved, and over which protocol. It's one of the first places you look when you suspect unusual behavior, for SOC Analysts.


The Dataset: What I Was Working With


I explored the schema before writing a single query. Here are the key fields in conn.log and why each one matters from a security perspective:

Field

What it contains

Why security analysts care

ts

Timestamp of the connection

Timing patterns reveal automated or off-hours activity

uid

Unique connection identifier

Links records across multiple log types

orig_h

Originating (source) IP address

Who initiated the connection

resp_h

Responding (destination) IP address

Where are they going — internal or external?

proto

Protocol (TCP, UDP, ICMP)

Unusual protocols can indicate tunneling or scanning

service

Application-layer service (HTTP, DNS, SSH)

Identifies what kind of traffic this really is

duration

How long the connection lasted

Very long or very short sessions can both be red flags

orig_bytes

Bytes sent by the source

Large outbound transfers = potential data exfiltration

resp_bytes

Bytes sent by the destination

Large responses may signal data staging or C2 activity

conn_state

State of the connection at close

Rejected or half-open connections suggest scanning

Setting Up SQLite on My Mac


I want to be honest about how this actually went, because the polished "here are the steps" version leaves out the part where you learn the most.


In the terminal, I used Linux to navigate to the folder containing the downloaded log files. I ran the ls command to view the folder’s contents, then launched SQLite directly from the terminal. I launched SQLite and ran my import commands. I ran SELECT COUNT(*) FROM conn_logs; and there was no output. What actually happened: I had typed my folder name wrong in the terminal. Whoops, make sure you type the right folder name. This mishap nearly derailed the whole session. Learning to debug your environment is as important as knowing the queries.


The second challenge: Zeek's conn.log is not a clean CSV. It opens with metadata lines starting with # that SQLite imported as actual data rows, meaning my column names became timestamps and IP addresses. The fix was to strip those lines first:


# Strip Zeek metadata header lines
grep -v "^#" conn.log > conn_clean.csv

Then I created the table with proper column names before importing. Schemas are defined before data is loaded.


-- Create the table first, then import
CREATE TABLE conn_logs (
  ts TEXT, uid TEXT, orig_h TEXT, orig_p TEXT,
  resp_h TEXT, resp_p TEXT, proto TEXT, service TEXT,
  duration TEXT, orig_bytes TEXT, resp_bytes TEXT,
  conn_state TEXT, local_orig TEXT, local_resp TEXT,
  missed_bytes TEXT, history TEXT, orig_pkts TEXT,
  orig_ip_bytes TEXT, resp_pkts TEXT, resp_ip_bytes TEXT,
  tunnel_parents TEXT
);
.mode tabs
.import conn_clean.csv conn_logs

I ran my SELECT statement, and it returned 1,319,960 log records.


Query 1: Where I Started — Connection States


Connection states tell you what happened at the end of every network session. This is the first query I ran on the network dataset. This query is like taking the pulse of the network.

SELECT conn_state, COUNT(*) AS total
FROM conn_logs
GROUP BY conn_state
ORDER BY total DESC;

My results from the query and what they mean. I also used AI to help me digest the results:

State

Count

What it means

S0

647,224

Connection attempted — no response. Classic port scanning.

SF

259,992

Normal completed connection. This is healthy traffic.

REJ

227,555

Connection actively refused. Scanning hitting closed ports.

OTH

142,044

No SYN seen — partial traffic, possible tunneling.

RSTO

13,639

Reset by originator — abrupt termination.

The results show that the failed connections (S0, REJ, OTH, RSTO) far outnumber successful ones (SF), this means over 60% of connections in the dataset never completed successfully.


Query 2: Finding the Source


With 647,224 S0 connections flagged, the next question was simple: who is responsible?


SELECT orig_h, COUNT(*) AS total
FROM conn_logs
WHERE conn_state = 'S0'
GROUP BY orig_h
ORDER BY total DESC
LIMIT 10;

The top result: 192.168.10.43 — 513,865 S0 connections. This meant that one internal IP was responsible for 79% of all unanswered connection attempts. That's not a human user - it's an automated, malicious machine. When I checked what it was attacking, I found it was systematically scanning four internal subnets and also reaching out to external IPs. Scanning at that volume from an internal host is exactly how a botnet spreads. (What those external connections actually were — and whether any amount to command-and-control — is what Part 2 investigates. Part 1 establishes the scanning; it doesn't prove a controller.)



Query 3: Identifying the Attack — Port Analysis


Knowing who was scanning told me the what. Knowing which ports they were hitting told me the why.


SELECT resp_p, COUNT(*) AS attempts
FROM conn_logs
WHERE orig_h = '192.168.10.43'
AND conn_state = 'S0'
GROUP BY resp_p
ORDER BY attempts DESC
LIMIT 10;

Port

Attempts

Service

23

157,065

Telnet — Mirai's primary target

22

123,913

SSH — brute force credential attempts

443

51,713

HTTPS — scanning web-facing devices

2323

17,381

Alternative Telnet — classic Mirai port

2222

14,144

Alternative SSH — same pattern

The chart shows massive scanning on port 23 and port 2323 — that's classic Mirai behavior. Mirai hunts internet-connected devices — cameras, routers — that still use factory-default credentials, breaks in, and conscripts them to launch DDoS attacks. The SSH attempts on 22 fit too; some Mirai variants use it. So this isn't just random scanning — it's a known, named threat.


💡 What is Mirai?

Mirai is malware from 2016 that hunts for smart devices (cameras, routers, etc.) still using factory default passwords. It takes them over and turns them into a botnet — an army of hacked devices — which then floods websites with traffic until they crash. Mirai took down major parts of the internet, including the Dyn DNS service. When its code was released publicly, copycats created many versions that are still active today.


Query 4: The Complete Host Summary


I wanted one query that profiled the whole network at once — every host, its connections, failed scans, successes, ICMP, and success rate.


SELECT
  orig_h,
  COUNT(*) AS total_connections,
  SUM(CASE WHEN conn_state = 'S0' THEN 1 ELSE 0 END) AS failed_scans,
  SUM(CASE WHEN conn_state = 'SF' THEN 1 ELSE 0 END) AS successful,
  SUM(CASE WHEN proto = 'icmp' THEN 1 ELSE 0 END) AS icmp_recon,
  ROUND(SUM(CASE WHEN conn_state = 'SF' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) AS success_rate_pct
FROM conn_logs
GROUP BY orig_h
ORDER BY total_connections DESC
LIMIT 10;

Host

Total

Failed Scans

Successful

ICMP

Success Rate

Verdict

192.168.10.43

894,218

513,865

158,154

36,682

17.7%

Primary threat actor

192.168.10.50

251,368

111,382

0

36,633

0%

Confirmed botnet node

10.200.200.80

40,615

20,868

1,643

4,040

4%

Likely third infected host

192.168.61.21

24,567

3

23,238

55

94.6%

Normal — baseline host

54.243.185.88

10,564

0

10,562

0

99.9%

High-volume outbound HTTP — likely a normal client, not a threat


Only .43 fits the Mirai signature cleanly — it's the one hammering telnet-family ports at scale, and it's the confirmed infection. The others are more nuanced than a single success-rate number suggests. 192.168.10.50 is scanning too, but on HTTPS and ICMP rather than telnet, which reads as internal reconnaissance — Part 2 digs into this. 10.200.200.80 is also sweeping, but again not on Mirai's ports; a scanner, yes, but a Mirai node isn't established. And 54.243.185.88, which at first glance looked like it could be a command-and-control server, turns out to only originate outbound HTTP connections — the opposite of what a C2 endpoint looks like (a controller is something infected hosts connect to). The honest read from this one query: one confirmed Mirai infection (.43), plus additional anomalous hosts whose exact roles need the deeper analysis in Part 2.


What Part 1 Reconstructed

From four queries, here's what the data supports — stated as what I could confirm, not beyond it:


Scanning (the confirmed core). 192.168.10.43 made over half a million connection attempts that got no reply, hammering Telnet (23), SSH (22), and alternate Telnet (2323) — the classic Mirai playbook. This is the confirmed infection.

Additional scanning hosts. 192.168.10.50 and 10.200.200.80 also show heavy scanning, but on HTTPS/ICMP rather than telnet — a different pattern (discovery/enumeration) that Part 2 examines. Whether these are additional infections or something else isn't settled by Part 1 alone.

Outbound external traffic. Several external IPs show high connection volume. Part 1 isn't enough to classify them — identifying genuine command-and-control requires the beaconing analysis in Part 2, which is where the actual C2 servers turn up.

An earlier version of this section described lateral movement across subnets, a fixed 999-byte "heartbeat" packet, and an AWS-hosted C2 server. On re-analysis those specifics didn't reproduce against the data, so I've removed them. The confirmed C2 finding — a host beaconing to four external servers on a fixed cadence — is in Part 2.

The Closing Number — 17.7% vs 94.6%


I ran one final comparison between the primary infected host and the cleanest host on the network:

Host

Total Connections

Success Rate

Verdict

192.168.61.21

24,567

94.6%

Normal user — completing nearly every connection

192.168.10.43

894,218

17.7%

Infected host — failing 82% of connections

A healthy machine completes nearly every connection it attempts. An infected machine fails 82% of the time because it is not trying to communicate — it is trying to find unlocked doors. That single percentage point difference is the difference between a user browsing the internet and a machine conscripted into a botnet without anyone knowing.


Analyst summary - what the data supports:

Host

Role

Action

192.168.10.43

Confirmed Mirai Scanner

Isolate immediately

192.168.10.50

Heavy scanner — internal recon (HTTPS/ICMP)

Isolate and investigate

10.200.200.80

External scanner — non-Mirai discovery

Investigate

192.168.61.21

Normal host

No action needed


(Two external IPs I'd initially listed as "suspected C2 servers" were removed — both only originate outbound traffic, which doesn't fit a command-and-control profile.)


🔗 ISC2 CC Domain Connections


This investigation maps directly to three exam domains: Domain 1 — Security Principles (CIA triad violation, availability threatened via DDoS recruitment); Domain 4 — Network Security (unauthorized scanning and internal reconnaissance); Domain 5 — Security Operations (incident identification and response).


🪞 Reflection questions I am sitting with


  1. How would this investigation look different in a SIEM like Splunk? What would I catch faster — and what might the dashboard hide?

  2. The dataset captures the attack in progress but not the origin. How would a real analyst trace back how 192.168.10.43 first became infected?

  3. 192.168.10.50 was scanned heavily with almost no successful connections. At what point does a SOC analyst isolate a host versus observing it longer to learn more?

  4. Several external IPs showed high outbound volume. How do analysts distinguish normal cloud and web traffic from malicious command-and-control?


What I Would Do Differently Next Time


Establish a baseline deliberately. I found my clean host accidentally. A disciplined approach documents what normal looks like — byte ranges, expected protocols, typical durations — before hunting anomalies.


Separate the number from the verdict. My biggest lesson: a query result and the conclusion drawn from it are two different things. "Zero successful connections" is a fact; "confirmed botnet node" is a hypothesis that needs its own query to test. Several of my first-pass verdicts were right about something being off but wrong about what — and the fix was always another query, not a better guess.


Cross-reference the DNS log. I downloaded dns.log too. Joining it with conn.log would reveal which domains those external IPs resolved to — a key step in confirming infrastructure by name.


There is a Part 2. The deeper analysis uncovered C2 beaconing on a fixed cadence, a second host mapping the network from the inside, external SSH brute-force campaigns, and one external IP that held a multi-hour session inside the network — a confirmed intrusion. That story is its own entry.


💼 From the Analyst's Desk to the SOC Floor


I started with nothing but a raw, messy log file from Zeek — no security software, no dashboards, no ready-made alerts. From that alone, I identified a Mirai botnet infection, isolated the primary infected host, confirmed the malware family by its port signature, and closed with a success-rate comparison that makes the evidence hard to ignore. Just as important: where my first-pass classifications ran ahead of the data, I went back, re-ran the queries, and corrected them. Building a detection narrative from raw data — and knowing when your own conclusion doesn't hold — is the skill that matters most.


The instincts I built over years in data analytics — spotting outliers, finding patterns, questioning anomalies — didn't need to be thrown away for cybersecurity work. They just needed to be pointed in a new direction, and disciplined with one rule: run it before you believe it.


Keep reading & see the code


→ Part 2

Going Deeper — Beacons, a Second Host, and What the Data Confessed — C2 beaconing on a fixed cadence, a second infected host mapping the network from the inside, external SSH brute force, and the one IP that actually got in.

Comments


Let's learn this together. Have a question, a better query, or just want to say hi? Drop a line below.

© 2026 by DataSec Chronicles. Data-Inspired, Instinct-Driven.    Privacy Policy    Terms & Conditions

bottom of page