Skip to content
Live2026Solo build — infrastructure, pipeline, detections

Internet-facing SSH honeypot → Microsoft Sentinel

An SSH honeypot disguised as a finance-database server, wired into Sentinel through a CEF pipeline — built to produce genuine attacker telemetry for detection engineering.

Azure VMnftablesSuricataCowrieCEFMicrosoft SentinelData Collection RulesKQLMITRE ATT&CK
0
evidence layers
0
analytics rules
minutes
to first brute force

The problem

Detection engineering needs attacker telemetry, and simulated telemetry is authored telemetry — every field is something a person decided to put there. A rule tuned against it is tuned against an assumption.

An internet-facing honeypot fixes that. The attackers are real; only the target is fake. Every credential tried, every command typed, every packet is something an actual adversary — a bot or a person — chose to do, unprompted, against a host they found on their own. Within minutes of the first build going live it was being brute-forced from several countries.

The design

One small Ubuntu VM on an unpeered virtual network — nothing it can reach if it's compromised — with TCP/22 open to the internet on purpose and real SSH moved to a high port. It wears a cover identity: a finance-database server, with a naming convention chosen up front to survive Azure's platform-populated _ResourceId column.

Four independent layers of evidence, all landing in one Sentinel table:

LayerToolContributes
ApplicationCowriean emulated shell — records every credential and command, executes nothing
Network IDS/IPSSuricata (ET Open)signature detection inline via NFQUEUE, so it can drop as well as alert
Host firewallnftablesreal allow/deny decisions with logging, and the hand-off to Suricata
Normalisation3 Python CEF convertersCowrie / Suricata / firewall events → CEF on syslog local4CommonSecurityLog

Everything queries CommonSecurityLog through a Data Collection Rule — nothing regex-scrapes raw Syslog.

What got built

The deception content is planted to be cross-consistent: RSA keys that are cryptographically valid but authorise nothing, a .bash_history showing an admin who ran mysqldump with an inline password then history -c, a backup.sh carrying the same password, an .env with a connection string. An attacker who reads the history, extracts the password and reuses it produces a complete "found a leaked credential → reused it" narrative across two rules — a far better training incident than any single event.

On top: ten analytics rules covering credential attack, post-compromise activity, network reconnaissance and cross-source correlation.

What broke

  • A rebuild forced by an un-renameable resource ID. The cover identity had to be baked in from the first deployment, not bolted on.
  • Every rule filtered on Computer == "<the old hostname>". When the VM was rebuilt under the disguised name, all ten rules would have silently gone dark — still enabled, still running, matching zero rows forever. The fix: filter on DeviceVendor and DeviceEventClassID, which describe what the event is and survive a rename. Computer is an output column, not a predicate.
  • A CEF key that silently emptied a column. The converters emitted deviceAction=; the real key is act=. CEF dumps unrecognised keys into the AdditionalExtensions catch-all with no error, so DeviceAction was blank on every firewall row and a "Denied" filter could never have matched.
  • A port-scan rule that never fires — because the NSG in front of the host drops traffic to those ports before the firewall's deny rule ever sees it. The counter is inert by design, not broken.
  • A few thousand log rows leaked in a twenty-minute window before a converter fix — caught on the ingestion volume, not by reading logs.

What it taught

Naming has to survive the columns the platform populates for you. Detections should key on event identity, never on a hostname. When a column you expect is blank, look in the catch-all field before assuming the value never arrived. And capture everything, filter the display — a capture filter is a permanent decision made before you know what you're looking for.