We Caught a Bot Fingerprinting Open-Source Honeypots — Here's Exactly How It Works
A new bot is systematically identifying open-source honeypot installations before it decides whether to attack. It checks for Cowrie, Kippo, Dionaea, Honeytrap, Glastopf, and OpenCanary — the six most widely deployed open-source honeypots — and it does it in under 45 seconds.
We captured the full session on our sensors. At the time of writing, Googling the bot's detection commands returns exactly one result: our own detection catalog. Nobody else is documenting this.

This post breaks down every command the bot runs, which honeypots each check would actually catch, and what this means for the open-source honeypot community.
The Full Session
The bot connects over SSH, authenticates, and immediately runs a scripted sequence of 44 commands in ~44 seconds — one command every ~720ms, no variation. This is fully automated, not a human operator.
The session breaks into four distinct phases.
Phase 1: System Fingerprinting (Commands 1–26)
The bot builds a profile of the target system:
netstat -tulpn | head -10
ssh -V
cat /etc/os-release 2>/dev/null | head -6
cat /etc/issue 2>/dev/null
ls /dev | wc -l
ls /var/log 2>/dev/null | wc -l
env | head -10
grep MemTotal /proc/meminfo 2>/dev/null
df -h / 2>/dev/null | tail -1
whoami
pwd
ls -la /
uptime
last -n 3 2>/dev/null | head -3
ls /opt 2>/dev/null
hostname
ps aux | head -15
history | tail -5
mount | head -5
cat /proc/version 2>/dev/null
grep 'model name' /proc/cpuinfo 2>/dev/null | head -1
crontab -l 2>/dev/null | head -5
uname -a
netstat -tulpn 2>/dev/null | grep LISTEN | head -20
nproc 2>/dev/null || grep -c '^processor' /proc/cpuinfo 2>/dev/null
ls -la /etc/network/interfaces /etc/sysconfig/network-scripts/ /etc/netplan/ 2>/dev/null | head -5This is standard reconnaissance — OS version, hardware specs, running services, logged-in users, network configuration. Nothing unusual yet.
Phase 2: Environment Validation (Commands 27–37)
This is where it gets interesting. The bot runs checks that test whether the environment behaves like a real Linux system:
ip addr show 2>/dev/null | grep -E '^[0-9]+:' | head -5
ip route show 2>/dev/null | head -3
echo 'test' > /tmp/test_1771787088
rm -f /tmp/test_1771787088
cat /etc/passwd 2>/dev/null | head -1
cat /etc/shadow 2>/dev/null | head -1
cat /proc/version 2>/dev/null | head -1
id
whoami
pwd
echo 'testpipe' | grep 'testpipe' | wc -cThe file write test (echo > /tmp/test_xxx && rm) checks if the filesystem is actually writable. The pipe test (echo | grep | wc) checks if shell pipes work correctly. These are anti-honeypot checks — emulated shells often break on edge cases like these.
Phase 3: Capability Assessment (Commands 38–41)
cat /proc/cpuinfo | grep 'model name' | head -1
which apt
which yum
which pacman
which zypper
systemctl list-units --type=service --state=running 2>/dev/null | head -10
time dd if=/dev/zero of=/tmp/testio bs=1M count=10 2>&1
rm -f /tmp/testio
ss -tuln 2>/dev/null | wc -l
cat /etc/passwd 2>/dev/null
awk -F: '$3>=1000 && $7 !~ /nologin|false/ {print $1}' /etc/passwd 2>/dev/null | wc -l
wget -q --timeout=3 -O /dev/null http://192.0.2.1/nonexistent 2>&1 | head -1The dd benchmark tests I/O performance — a honeypot with no real disk will return instant results with unrealistic throughput. The wget to 192.0.2.1 (an RFC 5737 TEST-NET address that's guaranteed unroutable) tests whether outbound network connections actually work or just return fake responses. The package manager checks (which apt/yum/pacman/zypper) profile what software can be installed on the target.
Phase 4: Honeypot Directory Scans (Commands 42–44)
This is the payload — the explicit honeypot detection:
test -d /opt/cowrie 2>/dev/null && echo EXISTS
test -d /opt/kippo 2>/dev/null && echo EXISTS
test -d /opt/honeytrap 2>/dev/null && echo EXISTS
test -d /opt/dionaea 2>/dev/null && echo EXISTS
test -d /home/cowrie 2>/dev/null && echo EXISTS
test -d /home/kippo 2>/dev/null && echo EXISTS
test -d /opt/glastopf 2>/dev/null && echo EXISTS
test -d /opt/opencanary 2>/dev/null && echo EXISTS
ps aux 2>/dev/null | grep -E 'tcpdump|snoopy|auditd' | grep -v grep
test -f /etc/cowrie/cowrie.cfg 2>/dev/null && echo EXISTS
test -f /home/cowrie/cowrie.cfg 2>/dev/null && echo EXISTS
test -f /opt/cowrie/etc/cowrie.cfg 2>/dev/null && echo EXISTSThe bot checks for six honeypot projects by name, probes three different Cowrie config file locations, and looks for monitoring tools (tcpdump, snoopy, auditd) that are commonly deployed alongside honeypots.
Which Honeypots Would Actually Get Caught?
We researched each honeypot's default installation paths and known fingerprinting weaknesses. The results are worse than you'd expect.
Cowrie (~5,000 GitHub stars, actively maintained)
Cowrie is the most popular SSH honeypot by far. The standard installation creates a cowrie user with a home directory at /home/cowrie/, and the config file lives at etc/cowrie.cfg relative to the install root. Docker and T-Pot deployments commonly use /opt/cowrie.
Directory check verdict:
test -d /home/cowrie— matches standard installationstest -d /opt/cowrie— matches Docker/T-Pot deploymentstest -f /opt/cowrie/etc/cowrie.cfg— matches Docker/T-Pot deployments
But here's the real problem: Cowrie's default shell backend doesn't implement the `test` shell builtin. As of Cowrie v2.9.11 (February 2025), test is not in the command module registry (56 modules, none for test) and has no txtcmd stub. On a real Linux system, test is always available — it's a bash builtin. On Cowrie, running test -d /opt/cowrie returns -bash: test: command not found. The directory check fails, but the failure itself is an even bigger tell.
The bot doesn't even need the directory to exist. The command not found error for a universal bash builtin immediately flags the system as a honeypot.
A note on Cowrie's alternative backends: Cowrie supports three backend modes (backend = shell | proxy | llm in cowrie.cfg). The default shell mode uses the built-in command registry analyzed below. The proxy mode forwards the attacker's session to a real contained host, which eliminates emulation tells entirely since the attacker interacts with an actual Linux system. The experimental llm mode routes every command to an OpenAI-compatible API, generating dynamic output — so test would produce a realistic response. However, proxy mode requires maintaining a backend VM, and llm mode is opt-in, requires an API key, costs money per interaction, and introduces its own fingerprinting surface (API round-trip latency per command, no persistent filesystem state, inconsistencies across related commands). The analysis below applies to Cowrie's default shell backend.
Other detection vectors that would catch default Cowrie:
| Check | What the bot sees | What a real system shows | Detection strength | |
|---|---|---|---|---|
| `ls /dev \ | wc -l` | ~150 entries (static pickle filesystem from 2013) | ~230 entries (dynamic udev devices) | Moderate — noticeable gap, but not as dramatic as other tells |
time dd if=/dev/zero of=/tmp/testio bs=1M count=10 | time: command not found (not implemented) | Benchmark output with realistic I/O speed | Strong | |
nproc | Always returns 2 (static txtcmd stub) | Actual CPU count | Fingerprintable — static value regardless of system profile | |
grep MemTotal /proc/meminfo | Static 4,054,744 kB across all instances | Varies per system | Fingerprintable |
Cowrie's /proc/meminfo is a static file shipped with the project. Every uncustomized Cowrie instance reports the exact same memory — and its free command reads the real host's /proc/meminfo instead, so an attacker comparing free output to cat /proc/meminfo would see mismatched values. If you're running Cowrie and haven't changed this file, you're broadcasting it.
Kippo (~1,700 GitHub stars, abandoned since 2015)
Kippo is Cowrie's predecessor and is no longer maintained. Its README points users to Cowrie as the actively developed successor. Standard installation uses /home/kippo/.
Directory check verdict:
test -d /home/kippo— matches standard installations- Same
testbuiltin problem as Cowrie —command not foundgives it away regardless
Kippo has even fewer implemented commands than Cowrie, a more limited fake filesystem based on the same Debian 5.0 snapshot, and well-documented fingerprinting methods that SANS ISC published years ago. If you're still running Kippo, this bot is the least of your problems.
Dionaea (~787 GitHub stars, last updated 2021)
Dionaea is a multi-protocol honeypot focused on catching malware and shellcode. Its official documentation specifies /opt/dionaea as the default cmake install prefix.
Directory check verdict:
test -d /opt/dionaea— matches standard source installations
However, Dionaea is not an SSH honeypot. It doesn't provide shell access. This check would only work if the bot has access to the host operating system through another vector — implying the bot is checking whether the machine it's on is also running other types of honeypots alongside whatever SSH service let it in.
Honeytrap (~1,200 GitHub stars for the Go framework)
There are two projects named Honeytrap. The original C-based version (~88 stars) installs to standard system paths via make install, not /opt. The Go framework is typically containerized.
Directory check verdict:
test -d /opt/honeytrap— unlikely to match either version in a standard install
Glastopf (~580 GitHub stars, abandoned)
Glastopf is a web application (HTTP) honeypot, not SSH. It's been deprecated in favor of SNARE and TANNER. The install directory is user-chosen, often under /opt/.
Directory check verdict:
test -d /opt/glastopf— might match if the operator cloned the source there
Like Dionaea, Glastopf doesn't provide SSH access, so this check targets the host OS.
OpenCanary (~2,800 GitHub stars, actively maintained by Thinkst)
OpenCanary is installed via pip. The Python package goes into site-packages, not /opt/opencanary. More importantly, OpenCanary's SSH module is low-interaction — it captures login attempts but never provides a shell. An attacker can't run any commands.
Directory check verdict:
test -d /opt/opencanary— does not match standard pip installations- Even if the directory existed, the bot couldn't reach a shell prompt to check
The Bigger Picture
This bot isn't just checking for one honeypot. It's running a comprehensive anti-honeypot toolkit that targets the entire open-source honeypot ecosystem in a single pass. That's a significant escalation from the individual tricks that have been documented before.
The implications:
Open-source honeypots are being actively filtered. We've observed these honeypot detection commands over 300 times across our sensor network. Each time the pattern appears, it's the same scripted sequence — automated, not manual. When a bot like this flags a honeypot, it moves on. The honeypot never sees the interesting behavior — the payload deployment, the lateral movement, the data exfiltration. It only captures traffic from bots that don't bother checking.
Honeypot data is biased, and operators don't know it. If sophisticated attackers are silently skipping your honeypot, your threat intelligence data only represents unsophisticated actors. You're drawing conclusions about attacker behavior from a self-selected sample that excludes the most capable threats.
The checks target multiple honeypot types simultaneously. Dionaea, Glastopf, and OpenCanary aren't SSH honeypots — but the bot checks for them anyway. This means it's profiling whether the host is a dedicated honeypot machine running multiple trap services. Multi-honeypot deployments like T-Pot are especially vulnerable to this — if one honeypot is detected, the attacker knows every service on the box is fake.
Why We Caught This
SikkerAPI's sensors are purpose-built, not based on any open-source honeypot framework. There are no Cowrie directories, no Kippo configs, no static /proc/meminfo values, no missing shell builtins. The test command works. The dd benchmark returns plausible I/O numbers. Command outputs are consistent with the system profile being emulated.
The bot ran its full detection suite, got clean results on every check, concluded it was on a real system, and continued with its post-authentication behavior.
This is the fundamental advantage of custom honeypot infrastructure over open-source deployments: attackers can't fingerprint what they've never seen the source code for.
Recommendations for Honeypot Operators
If you're running Cowrie or another open-source honeypot, this bot is probably already skipping your infrastructure. Here's what you can do:
- Implement the `test` shell builtin. This is the single biggest tell. Every real Linux system has it. If your honeypot returns
command not foundfortest,[, ortime, you're trivially detectable. - Make static commands dynamic. Cowrie's
nprocalways returns2regardless of the system profile being emulated. If your honeypot claims to be a 64-core Threadripper butnprocsays2, that's a giveaway. Ensure command outputs are consistent with your system persona. - Regenerate your filesystem snapshot. Cowrie's default
fs.picklewas captured from a Debian system in 2013. The/devdirectory has ~150 entries vs ~230 on a modern system — a detectable gap. Regenerate from a current OS image using Cowrie'screatefs.pyutility. - Randomize `/proc/meminfo` per instance. Don't ship the same static 4GB memory value on every deployment. Generate realistic values during installation. Also note that Cowrie's
freecommand reads the real host's/proc/meminfowhilecat /proc/meminforeads the static file — an attacker comparing both sees mismatched values. - Make `dd` and `time` work. Even if the output is fake, it should exist and return plausible values. A missing
timebuiltin is as suspicious as a missingtest. - Don't install to default paths. If you must use Cowrie, don't install it in
/home/cowrie/or/opt/cowrie/. Use a non-obvious path. Change the user account name. - Consider proxy mode over emulation. If patching individual emulation gaps feels like whack-a-mole, Cowrie's
proxybackend (backend = proxyincowrie.cfg) forwards attacker sessions to a real contained host. The attacker interacts with an actual Linux system, eliminating the emulation tells entirely. The tradeoff is operational complexity — you're maintaining a real VM that attackers have access to, but the fingerprinting surface drops to near zero. - Monitor for these fingerprinting patterns. If you see this exact command sequence in your honeypot logs, the attacker has already identified you. The valuable data is that they checked, not what they did afterward (because they likely disconnected).
Full Detection Checklist
Every command the bot runs, what it's testing, and which honeypots it would catch:
| Command | Tests for | Catches | |||
|---|---|---|---|---|---|
| `ls /dev \ | wc -l` | Realistic device count | Cowrie, Kippo (~150 vs ~230 on real system) | ||
| `ls /var/log \ | wc -l` | Realistic log count | Cowrie, Kippo (static 2013-era filesystem) | ||
echo 'test' > /tmp/test_xxx | Filesystem writability | Basic honeypots without write support | |||
| `echo 'testpipe' \ | grep \ | wc` | Pipe functionality | Kippo (limited pipe support) | |
time dd if=/dev/zero bs=1M count=10 | I/O benchmark + time builtin | Cowrie (time not implemented) | |||
wget http://192.0.2.1/nonexistent | Outbound network (TEST-NET) | Honeypots without real outbound | |||
grep MemTotal /proc/meminfo | Memory fingerprint | Cowrie (static 4GB across all instances) | |||
nproc | CPU count consistency | Cowrie (always returns static 2) | |||
test -d /opt/cowrie | Cowrie directory | Cowrie (Docker/T-Pot installs) | |||
test -d /home/cowrie | Cowrie directory | Cowrie (standard installs) | |||
test -f /opt/cowrie/etc/cowrie.cfg | Cowrie config | Cowrie (Docker/T-Pot installs) | |||
test -f /home/cowrie/cowrie.cfg | Cowrie config | Cowrie (non-standard) | |||
test -f /etc/cowrie/cowrie.cfg | Cowrie config | Cowrie (non-standard) | |||
test -d /opt/kippo | Kippo directory | Kippo (non-standard path) | |||
test -d /home/kippo | Kippo directory | Kippo (standard installs) | |||
test -d /opt/honeytrap | Honeytrap directory | Unlikely to match | |||
test -d /opt/dionaea | Dionaea directory | Dionaea (standard installs) | |||
test -d /opt/glastopf | Glastopf directory | Possible if source-cloned there | |||
test -d /opt/opencanary | OpenCanary directory | Does not match pip installs | |||
| `ps aux \ | grep tcpdump\ | snoopy\ | auditd` | Monitoring tools | Ambiguous (common on real servers too) |
Explore Our Detection Data
SikkerAPI's threat detection catalog already has primitives matching every phase of this bot's behavior — from the honeypot directory probes to the environment validation checks. You can look up any IP against our database using the IP reputation API or the free lookup on the homepage.
To see real-time SSH attack patterns across our global sensor network, browse the SSH threat data. For automated blocking, see our Nginx, iptables/ipset, Fail2Ban, and CSF integration guides, or use the sikker CLI to query threat data from your terminal.
Comments
No comments yet. Be the first to share your thoughts!