One internet-facing honeypot recorded this in seven days:
- 14,469 sessions
- 11,597 accepted logins
- 18,312 commands
- 562 downloaded files
- 20,452 Telnet-protocol events
- 250 sessions that requested an interactive terminal
That last number was the useful one.
Only about 1.7% of the sessions negotiated a pseudo-terminal. The other 98.3% did not request a pseudo-terminal; most of the sessions I reviewed in that population connected, fired a fixed command list, and left. That gave me a cheap first-pass way to separate commodity automation from sessions worth reading closely.
It was not proof of a human. Some tools request a terminal, and a human can work without one. But it reduced 14,469 sessions to a review set I could actually decompose.
Inside that smaller set I found four different behavior classes: bulk IoT loaders, semi-automated recon tooling, likely human-operated activity that used a planted credential in an attempted lateral move, and a much less skilled operator apparently pasting commands from a guide.
Everything described below happened inside Cowrie's emulated environment. None of the destructive commands reached the real host.
The traffic was mostly machinery
The largest bucket was familiar internet background radiation: Telnet-heavy IoT botnet traffic that connected, identified the architecture, downloaded several binaries, and disconnected.
One loader tried separate payloads for ARM, ARM7, MIPS, MIPSel, and x86. That is not an operator carefully selecting a binary. It is a script throwing one build per CPU family at the target and waiting to see what runs.
Another endpoint returned a different SHA-256 on repeated fetches of the same path. One source pulled that path over and over and received a nearly constant stream of new hashes. The useful observation was the delivery technique, not any single hash: server-side repacking makes a hash-only blocklist age badly.
Two high-volume sources did little beyond:
echo -e "\x6F\x6B"
uname -a
The first command asks the shell to turn hexadecimal bytes into ok. The second asks what architecture it landed on. Together they work as a cheap reality check before a loader commits more time and payloads.
This traffic was loud, repetitive, and mostly boring. That is useful. The more mechanical the baseline becomes, the easier it is to spot behavior that does not fit.
A five-minute rhythm exposed the semi-automated tier
One source looked human if I read only the command history.
It searched for a working editor in order:
nano → vi → vim → view → vimdiff
It fought heredoc quoting, checked terminfo, used file, xxd, od, and readelf to see whether binaries were real, looked for /.dockerenv, swept decoy files, and tried destructive actions.
The command choices looked investigative. The session mechanics did not.
A cluster of sessions ended at almost exactly 300,000 milliseconds: 304,215, 303,514, 301,512, 301,543, and 301,541 ms. Repeated five-minute boundaries strongly indicate a client-side timeout rather than a person repeatedly deciding to stop at the same instant.
Per-session decomposition was consistent with automated tooling slicing a human-authored playbook into fixed timeout windows.
This was the middle tier: more capable than an architecture-spraying loader, but still more consistent with machinery than live typing.
One session followed the complete decoy trail
The strongest likely human-operated behavior came from a source that worked through the filesystem over multiple sessions.
The operator enumerated application directories, read .env, docker-compose.yml, and Git configuration, checked users and services, and opened every credential-shaped decoy it could find.
That maps cleanly to file and directory discovery and credentials-in-files access in MITRE ATT&CK.[4][5]
The decisive moment came after the operator read a planted application credential. It did not merely collect the password. It used the exact decoy value in a database connection and then attempted SSH to the decoy database host.
That moved the session from discovery into attempted lateral movement over SSH.[6]
The chain was:
- Discovery — enumerate applications, identity, users, network, and services.
- Credential access — read decoy environment files, SSH material, and credential dumps.
- Privilege and identity attempts — try
sudo su -and switch to a decoy user. - Attempted lateral movement — use the planted database credential in connection attempts against the planted internal host.
- Destructive attempts — try
rm -rf, shutdown, reboot, and SysRq-style actions.
The timing mattered as much as the commands. Six sessions ran back-to-back, then stopped for more than three hours. When the source returned, it went directly to a specific credential file it had discovered earlier.
The operator also adapted when commands failed. After shutdown did nothing, it tried which, type, file, and direct reads of the shutdown binary to understand why. It created a file, verified it, and removed it as a permission test.
That is a different shape from a loader or a fixed five-minute framework. I still cannot prove who was behind the source address, whether the endpoint belonged to the operator, or whether it was a compromised residential device. The safe conclusion is behavioral: this session was likely human-operated.
The clumsy session was useful too
A second likely human-operated source followed much of the same decoy trail, but execution quality was lower.
It pasted strings containing literal \n characters into the shell. It typed a filename as if it were a command, tried less in an environment without less, then eventually reached cat. It also produced ls,-l and cycled through reboot commands without investigating why they failed.
Those mistakes are analytically useful. Copy-paste artifacts, typos, and recovery behavior can grade operator skill without any external attribution service.
The point is not to mock the operator. It is to distinguish a person following a checklist from a person troubleshooting the system in front of them.
The reused SSH key connected this week to a long-running campaign
The most common downloaded artifact in this dataset had this SHA-256:
a8460f446be540410004b1a8db4083773fa46f7fe76fa84219c93daa1669f8f2
It was written as an SSH authorized_keys file. SANS ISC reporting identifies the same hash and the mdrfckr key comment as a persistent Outlaw/Dota/Shellbot lineage first seen in 2018.[1] A second 2026 Cowrie report captured the exact same key being planted within a 22-second automated post-authentication sequence.[2]
Trend Micro's earlier Outlaw research documents the surrounding pattern: weak SSH or Telnet access, broad scanning, removal and recreation of .ssh, SSH-key persistence, reconnaissance, competitor cleanup, mining, and Shellbot control infrastructure.[3]
The dominant SSH-key artifact therefore matches a distinctive authorized_keys file publicly associated with the Outlaw/Dota/Shellbot lineage and carrying the comment mdrfckr. The match supports campaign-family clustering at the artifact level, but it does not identify the individual or organization behind the connection. Recurrence of the same key is consistent with repeated activity from that lineage, not proof of a single continuous operator.
It also corrected one thing in my first notes. Another frequent hash turned out to be the SHA-256 of a single newline. In this case it came from clearing /etc/hosts.deny, but it is not a unique malware fingerprint and should not be published as an IOC. The behavior matters; the hash alone does not.
The durable detection points are the authorized-key hash, the public-key comment, the command sequence, and the act of modifying authorized_keys—a recognized SSH persistence technique.[7] Clearing access controls belongs in the impair-defenses bucket.[8]
Three side cases I would have missed in a volume-only report
The weekly totals hid several smaller motives.
Possible resource misuse. One source fetched a legitimate Plex Media Server ARM64 package, attempted to install it, then ran top and htop. That behavior was consistent with an attempt to repurpose the decoy's resources, but the operator's motive cannot be established from these commands alone.
Proxy abuse. Another source logged in and immediately requested an SSH direct-tcpip forward to an external TLS endpoint. It ran no shell commands. The objective was network position: use the compromised host as a relay.
Deep filesystem exploration. Ten distinct sources reached a deliberately absurd file buried several directories deep. The content of that decoy was irrelevant. Reaching it was a useful proxy for recon depth.
These cases would disappear if I ranked only by connection count.
What I did not do
I did not touch, scan, or contact any source address or payload host.
I did not publish my sensor address, real administration path, or my own test address. Those details add operational risk without making the analysis more useful.
I did not call a residential source address a specific person. ISP assignment is not identity. It may represent a direct operator, a compromised router, carrier NAT, or something else entirely.
I did not treat a PTY request as proof of a human. It was a filter. Timing, adaptation, troubleshooting, pauses, and follow-through carried the classification.
I did not describe Cowrie's accepted logins as real compromises. The honeypot is designed to accept credentials and emulate a shell. The dataset shows what the actors attempted after the decoy let them in.
And I did not claim the real host was universally secure. The bounded conclusion is narrower: no activity in this reviewed dataset reached it, and every destructive command shown here executed against the emulation.
The boring triage method
If I had to repeat this analysis next week, I would use the same order:
- Count sessions, commands, downloads, and protocol events.
- Use PTY negotiation as a cheap first-pass interaction filter.
- Group session durations and look for mechanical timeout boundaries.
- Decompose commands per session instead of pooling them by source.
- Compare command variation, mistakes, recovery, and return behavior.
- Track whether decoy credentials are merely read or actually used.
- Hash captured artifacts, but do not confuse common-file hashes with campaign identity.
- Corroborate attribution through public research before naming a family.
- Separate observed behavior from source-IP identity.
- Preserve a longitudinal thread across weeks.
The weekly headline was 14,469 sessions. The useful finding was that 250 deserved a closer look, and only a small fraction of those behaved like someone was actually thinking at the keyboard.
That is the value of a honeypot for me. Not a wall of scary IP addresses. A controlled place to watch the difference between a loader, a framework, and an operator.
If you want the earlier host-side version of this workflow, I wrote up the read-only log triage I use after exploit probes hit a real server. This honeypot analysis is the other side of the same question: not just what knocked, but what it tried to do after the door appeared to open.
— Rich
Sources
- [1] New Malware Libraries means New Signatures
- [2] 22 Seconds to Compromise
- [3] Outlaw Updates: Kill Old Miner Versions, Target More
- [4] MITRE ATT&CK T1083 File and Directory Discovery
- [5] MITRE ATT&CK T1552.001 Credentials In Files
- [6] MITRE ATT&CK T1021.004 SSH
- [7] MITRE ATT&CK T1098.004 SSH Authorized Keys
- [8] MITRE ATT&CK T1562 Impair Defenses