The Problem With Volume-Based Severity
When Lab Log 006 validated the triage script against the Easy As 123 C2 pcap, it scored MEDIUM. The reasoning at the time: only 61.5KB of data had been sent to the C2 server, which fell below the CRITICAL threshold of 1MB. Low data volume, lower urgency.
That logic is wrong. Volume measures damage already done — it says nothing about the threat that is present right now. The Easy As 123 machine had a confirmed active C2 connection to 45.131.214.85, sending POST requests to /fakeurl.htm every 60 seconds for over four hours. The malware was installed, it had established contact with its operator, and it was waiting for a command payload. An attacker with access to that C2 server could have sent ransomware, a credential harvester, or a lateral movement tool at any moment.
The correct severity question is not "how much damage has occurred?" — it is "is a confirmed threat present?" A machine with an active C2 connection is CRITICAL regardless of byte count. The window is open. The severity logic needed to be corrected.
Easy As 123: 61.5KB sent → MEDIUM
Lumma Stealer: 2.27MB sent → CRITICAL
Flaw: Confuses "not much damage yet" with "not serious." A confirmed C2 beacon is always serious regardless of how many bytes have moved.
Easy As 123: fakeurl.htm confirmed IOC → CRITICAL
Lumma Stealer: 5 confirmed IOCs → CRITICAL
Fix: Any match against KNOWN_BAD_DOMAINS immediately scores CRITICAL — threat confirmed regardless of data volume.
The KNOWN_BAD_DOMAINS List
The fix required a third configuration list alongside SUSPICIOUS_TLDS and SAFE_HOSTS. Where SUSPICIOUS_TLDS scores domains probabilistically based on TLD abuse patterns, KNOWN_BAD_DOMAINS matches specific confirmed IOCs — either from malware previously analyzed in these labs, or from infrastructure categories that are essentially never used by legitimate organizations.
| IOC | Type | Reason |
|---|---|---|
| fakeurl.htm | Confirmed IOC | Easy As 123 C2 URI — analyzed in Lab Log 003. CMD=POLL beaconing payload. |
| set_agent | Confirmed IOC | Lumma Stealer registration endpoint — analyzed in Lab Log 004. Browser fingerprint exfiltration. |
| whitepepper.su | Confirmed IOC | Lumma Stealer primary C2 domain. .su = Soviet Union TLD, criminal infrastructure. |
| communicationfirewall-security.cc | Confirmed IOC | Lumma Stealer secondary C2 domain. Deceptive name designed to appear legitimate. |
| holiday-forever.cc | Confirmed IOC | Lumma Stealer payload delivery domain. .cc = Cocos Islands, high abuse TLD. |
| whooptm.cyou | Confirmed IOC | Lumma Stealer C2 domain. .cyou = China You, extremely high abuse rate. |
| megafilehub | Confirmed IOC | Lumma Stealer payload hosting. Partial match catches all subdomains. |
| duckdns.org | Dynamic DNS | Free dynamic DNS. No identity verification. Widely abused by RATs and C2 infrastructure. |
| no-ip.com | Dynamic DNS | One of the most abused dynamic DNS providers globally. Rarely seen in legitimate enterprise traffic. |
| ddns.net | Dynamic DNS | Free subdomain service with no verification. Common in commodity malware C2. |
| hopto.org | Dynamic DNS | No-IP subdomain provider. Frequently flagged in threat intelligence feeds. |
| duckdns.org | Dynamic DNS | Popular with malware authors for fast infrastructure rotation. |
| zapto.org / sytes.net | Dynamic DNS | No-IP subdomain family. Near-zero legitimate enterprise usage. |
Why dynamic DNS providers belong on a bad list: Dynamic DNS lets anyone point a subdomain at any IP address and change it instantly — no registration, no identity verification. That flexibility is exactly what malware authors need: if a C2 IP gets blocked, they can redirect the domain to a new one within minutes. Legitimate enterprises with real infrastructure budgets and IT departments have no reason to use free dynamic DNS. Seeing any of these providers in enterprise network traffic is a strong indicator of malware or unauthorized software.
The get_known_bad_hits() Function
A new function was added to the script between the username lookup and the HTTP analysis steps. It checks both http.log and ssl.log against every entry in KNOWN_BAD_DOMAINS, deduplicates matches across both logs, and returns a list of confirmed hits with the matched IOC, the full host or domain, the destination IP, and the source log file.
def get_known_bad_hits(log_dir): """Check http.log and ssl.log for confirmed malicious IOCs.""" hits = [] seen = set() # prevents same host appearing twice if in both logs # Check http.log — match against host AND URI path http_path = os.path.join(log_dir, "http.log") if os.path.exists(http_path): for row in parse_zeek_log(http_path): host = row.get("host", "") uri = row.get("uri", "") ip = row.get("id.resp_h", "") combined = host + uri # fakeurl.htm is in the URI, not the host for ioc in KNOWN_BAD_DOMAINS: if ioc in combined and host not in seen: seen.add(host) hits.append({ "indicator": ioc, "host": host, "ip": ip, "source": "http.log" }) # Check ssl.log — match against SNI server_name field ssl_path = os.path.join(log_dir, "ssl.log") if os.path.exists(ssl_path): for row in parse_zeek_log(ssl_path): server_name = row.get("server_name", "") ip = row.get("id.resp_h", "") for ioc in KNOWN_BAD_DOMAINS: if ioc in server_name and server_name not in seen: seen.add(server_name) hits.append({ "indicator": ioc, "host": server_name, "ip": ip, "source": "ssl.log" }) return hits
Key Design Decision — Matching Combined Host + URI
The Easy As 123 C2 beacon posts to 45.131.214.85/fakeurl.htm. The IOC fakeurl.htm is in the URI path, not the host field. If the function only checked host, it would never match. By concatenating host + uri into a single string before checking, the function catches IOCs in either field with one comparison.
Severity Scoring Update
The severity logic was updated to treat any confirmed IOC match as CRITICAL — regardless of data volume:
# Known bad hits always escalate to CRITICAL if known_bad or total_sent > 1_000_000: severity_display = red(bold("CRITICAL")) elif total_sent > 100_000 or findings > 3: severity_display = orange(bold("HIGH")) elif findings > 0: severity_display = orange("MEDIUM") else: severity_display = "LOW"
The known_bad or condition means the script short-circuits to CRITICAL the moment any confirmed IOC is found — it does not wait to calculate data volumes first.
Validated Output — Both Pcaps
Lumma Stealer — 5 Confirmed IOC Matches
Analyzing Zeek logs in: /Users/yanai/Desktop/zeek-lumma [+] Host identity: done [+] Username: done [+] Known bad IOCs: done — 5 matches [+] HTTP analysis: done — 1 hits [+] TLS analysis: done — 5 hits ============================================================ ZEEK TRIAGE REPORT ============================================================ [ INFECTED HOST IDENTITY ] ------------------------------------------------------------ IP Address : 10.1.21.58 MAC Address: 00:21:5d:c8:0e:f2 Hostname : DESKTOP-ES9F3ML Username : gwyatt [ CONFIRMED MALICIOUS IOCs ] — 5 matches ------------------------------------------------------------ !! MATCH whitepepper.su 153.92.1.49 IOC: set_agent [http.log] !! MATCH media.megafilehub4.lat 104.21.48.156 IOC: megafilehub [ssl.log] !! MATCH whooptm.cyou 62.72.32.156 IOC: whooptm.cyou [ssl.log] !! MATCH holiday-forever.cc 80.97.160.24 IOC: holiday-forever.cc [ssl.log] !! MATCH communicationfirewall-security.cc 104.21.9.36 IOC: communicationfirewall-security.cc [ssl.log] [ SEVERITY SUMMARY ] ------------------------------------------------------------ Confirmed malicious IOCs : 5 Suspicious HTTP requests : 1 Suspicious TLS domains : 5 Total data exfiltrated : 2.27 MB Overall severity : CRITICAL
Easy As 123 — Severity Corrected From MEDIUM to CRITICAL
Analyzing Zeek logs in: /Users/yanai/Desktop/zeek-easy123 [+] Host identity: done [+] Username: done [+] Known bad IOCs: done — 1 matches [+] HTTP analysis: done — 1 hits [+] TLS analysis: done — 0 hits ============================================================ ZEEK TRIAGE REPORT ============================================================ [ INFECTED HOST IDENTITY ] ------------------------------------------------------------ IP Address : - MAC Address: 00:e0:4c:68:08:00 Hostname : brads-MBP Username : brolf [ CONFIRMED MALICIOUS IOCs ] — 1 matches ------------------------------------------------------------ !! MATCH 45.131.214.85 45.131.214.85 IOC: fakeurl.htm [http.log] [ SEVERITY SUMMARY ] ------------------------------------------------------------ Confirmed malicious IOCs : 1 Suspicious HTTP requests : 1 Suspicious TLS domains : 0 Total data exfiltrated : 61.5 KB Overall severity : CRITICAL
Why the Easy As 123 host field shows the IP twice: When malware connects directly to a raw IP address rather than a domain name, Zeek's http.log records the IP in both the host field and the id.resp_h field. There is no domain name to display because the malware never performed a DNS lookup — it hardcoded the C2 IP directly. This is itself a behavioral indicator: legitimate software almost always connects to domain names, not raw IPs.
Script Evolution — Lab 006 vs Lab 007
| Capability | Lab 006 | Lab 007 |
|---|---|---|
| Detection layers | TLD matching + safe host filtering | IOC matching + TLD matching + safe host filtering |
| Severity basis | Data volume only | Confirmed IOC presence OR data volume |
| Easy As 123 score | MEDIUM — incorrect | CRITICAL — correct |
| Lumma Stealer score | CRITICAL — correct | CRITICAL — correct |
| Configuration lists | 2 — SUSPICIOUS_TLDS, SAFE_HOSTS | 3 — adds KNOWN_BAD_DOMAINS |
| Functions | 5 analysis functions | 6 analysis functions — adds get_known_bad_hits() |
| Report sections | 4 sections | 5 sections — adds CONFIRMED MALICIOUS IOCs |
NIST SP 800-171 Control Mapping
Lessons Learned
Severity Logic Must Reflect Threat Reality, Not Just Damage Metrics
The MEDIUM score for Easy As 123 was a design flaw, not an analysis error. The underlying data was correct — host identity, C2 IP, request count, data volumes were all accurately identified. The mistake was in what the scoring asked: how much data moved, rather than is a threat confirmed. Security tooling that scores low damage as low severity will systematically undertriage active C2 connections that haven't yet executed their payload — exactly the most important moment to catch them.
IOC Lists Are Only Valuable If They Grow
The current KNOWN_BAD_DOMAINS list contains IOCs from two malware families analyzed across four lab sessions. Every new pcap analyzed is an opportunity to add confirmed indicators. Dynamic DNS providers were added not from specific malware analysis but from threat intelligence knowledge — they represent a category of infrastructure that is empirically associated with malicious activity. Both types of entries belong on the list, and both should be documented with their source so future analysts understand why each entry is there.
Next Lab
Lab Log 008 will build the Mac application wrapper — a drag-and-drop GUI that runs zeek_triage.py without any terminal interaction. The goal is a self-contained tool that an analyst at any experience level can use on any Zeek log folder.