Netfront #CVE-2023-54391 Incident Report: Proxmox VE authentication bypass, 22 hosts in a cluster compromised, and how we restored
Netfront — hosting.netfront.net All times HKT (UTC+8). Published September 2026.
Between 22:58 on 1 September 2026 and the end of the following day, at least 33 separate external addresses obtained root on our 22-node Proxmox VE cluster through CVE-2023-54391. Five of them reached every node and a sixth all but one, and commands were executed inside customer virtual machines running the guest agent. We rebuilt the platform and returned it to service on 7 September; that evening it was re-entered on credentials that had survived the rebuild. A full audit over 8–9 September found what had been left behind, and the last of it was removed on 9 September.
What follows is what happened, in the order we found it out. Several of the findings do not appear to be documented elsewhere — the first being that an eBPF rootkit can hide from rsync --delete, so a host restored from backup comes back still infected. That one is worth knowing before a cleanup rather than after.
Background
We run three zones. Zone C was still on Proxmox VE 7.4 and behind our upgrade rotation; PVE 7 reached end of life in July 2024. Port 8006 was internet-reachable on that cluster because our customers log into it to manage their own VMs.
CVE-2023-54391 is an unauthenticated authentication bypass in libpve-access-control before 8.0.4, CVSS v3.1 9.8: an arbitrary tfa-challenge value sent to the API login endpoint returns a valid ticket for any enabled account without a second factor, root@pam included. The vulnerable code was removed in July 2023; the advisory and CVE were published on 1 September 2026.
We were not the first — the advisory exists because victims spoke up, the first being a user whose PVE 7 host had been encrypted and its logs wiped. The first root shell on our cluster opened at 22:58 that evening, seven hours before the CVE record reached NVD.
1–2 September: the compromise
Our access logs run from 26 August, six days before the advisory. They contain a negative worth publishing: there was no targeted activity against us before the advisory — only our own infrastructure and untargeted background noise. Nobody was sitting on this flaw waiting for us.
Proxmox published PSA-2026-00043-1 at approximately 16:17 on 1 September (the exact posting time is not given in the advisory; this is derived from reporting placing it about fourteen hours before the NVD record). The first anomalous request against us arrived at 16:42 — twenty-five minutes later. Two scanning waves followed and neither achieved anything: thirteen DigitalOcean droplets attempting close to a thousand guest-exec calls, all 401, and 155.117.232.145 making 6,000 requests in an hour enumerating certs/info, all 501.
At 22:58 the first root shell opened. 223.118.51.66 had made five successful calls to /access/ticket fifteen minutes earlier and loaded the web console's JavaScript; at 22:58 it issued POST /nodes/xb1/termproxy and received a 200. That is a root shell on the hypervisor, through the browser. Roughly six and a half hours after the advisory.
Seventy-four minutes later, at 00:12 on 2 September, our file-modification detection recorded docker.io, git and needrestart installed by dpkg inside an 11-second window on that same node.
Anyone whose vulnerability pipeline is fed from NVD — where the record did not appear until 06:17 that morning — learned of this after we had already lost the platform.
Then it became crowded — and the obvious question is what we were doing for the rest of that day. We were investigating from early on; what we did not have was a cause. The hosts were not behaving like compromised machines, they were behaving like failing hardware, and establishing why took until the afternoon.
Counting from that first shell at 22:58 to the end of 2 September, 33 distinct external addresses obtained host root, with a thirty-fourth arriving early on 3 September. The measure here is POST /nodes/<node>/termproxy returning 200 — a root shell on the hypervisor itself, which no customer can reach under any permission set, as distinct from a VM console, which they legitimately can. Six addresses swept effectively the whole cluster — five reaching all 22 nodes, one reaching 21:
| address | window (2 Sep) | shells | nodes |
|---|---|---|---|
77.239.124.107 |
00:12 – 23:59 | 1,957 | 22 |
103.252.119.62 |
16:00 – 17:00 | 1,891 | 22 |
34.87.182.62 |
20:29 – 22:29 | 138 | 22 |
104.28.210.248 |
17:27 – 19:47 | 86 | 22 |
113.169.91.82 |
17:19 – 21:58 | 34 | 21 |
49.212.184.129 |
18:54 – 18:55 | 22 | 22 |
The signatures are not compatible with a single operator. 49.212.184.129 took exactly one shell on each of 22 nodes inside 61 seconds — a script walking the fleet. 77.239.124.107 worked steadily across the whole day. 103.252.119.62 compressed 1,891 shells into a single hour, 16:00 to 17:00, which happens to be the hour we discovered the intrusion. The remaining twenty-seven took between one and 86 shells each.
The sources are as varied as the behaviour: Tor exit relays, Cloudflare WARP, Google Cloud, Sakura, Vultr, Kamatera, Contabo and a dozen smaller hosting providers. 185.220.101.141, a Tor exit, took 36 root shells on a single node between 16:05 and 16:13.
We cannot attribute this to one party, and we do not believe it was one. Six independent fleet-wide sweeps, tempos ranging from 61 seconds to a full day, sources spread across Tor, consumer VPN and a dozen hosting providers — and a mix of payloads that does not resolve into one operation with one purpose.
Two structural facts made the spread inevitable once the first door opened. A cluster shares /etc/pve/priv/authorized_keys, so root on one node is root on all of them — there is no lateral movement step to detect. And the bypass required no credential, so every party that found the advisory had the same access as the first.
One other thread from the same day: at 00:21 192.236.217.18 began creating API tokens, eventually 39 of them from that one address.
Most of this did not look like API abuse. Some of it was clearly automated — the 61-second fleet walk above — but the larger presences worked the way an administrator does: mint a ticket, open a console, type. If you are watching for anomalous API call patterns, that is invisible to you, because after the authentication it looks like someone doing their job.
2 September: discovery, and the rootkit
We were already investigating. Our file-modification detection had fired, services were failing across the cluster, and we were working the problem — what we did not yet have was a cause. A process named supervisor carrying around 410 accumulated CPU-hours was the first thing that made sense.
The hosts had looked like failing hardware for hours — every command returning [1]+ Stopped, top dying with SIGSEGV, load averages of 30–160 on idle CPUs — yet dpkg -V reported top, procps and libc6 all clean.
That is the trap. The binaries verified clean because they were clean. The interception was in the kernel.
Every node carried an eBPF rootkit, loaded by a systemd unit with a random 8-character name and Description=System Management Daemon, reloading every two minutes. It hid its files and processes from ls, find and ps; blocked kill against its own PIDs; blocked ptrace, defeating strace and gdb; intercepted openat, read and getdents to return fabricated content; and rewrote Monero addresses in flight.
The Stopped flood was bpf_send_signal() sending SIGSTOP on syscall exit while the rootkit rewrote each process's userspace buffer. On the kernel we were running it misfired and fired thousands of times per second. That is what made the hosts look broken — and what eventually led us to the kernel rather than the hardware. On a kernel where it does not misfire, the same rootkit would have been silent, and we would have been chasing a cause that produced no symptoms at all.
The program and map names are not the author's own. They come from pathtofile/bad-bpf, a public collection of eBPF programs demonstrating bad behaviour presented at DEF CON 29 — pidhide for the getdents hook, sudoadd for the openat/read chain, and textreplace2 for the tail-called find_possible_addrs → check_possible_addresses → overwrite_addresses sequence doing the wallet substitution. A public educational toolkit, lightly assembled. That lowers the sophistication bar you should assume for whoever does this next.
Detection is one command, and the only check the rootkit cannot filter:
bpftool map show | grep -E 'protected_pids|exempt_comm|hidden_filename|ghost_files|hidden_names'
Any output means infected. If bpftool is missing, copy it from another node — on a host where dpkg keeps getting killed, apt will not finish. kernel.unprivileged_bpf_disabled=2 is irrelevant; whoever loaded it had root and CAP_SYS_ADMIN.
Removal: kill(2) does not work, because the rootkit hooks it. Use echo 1 > /sys/fs/cgroup/<path>/cgroup.kill, and disable, mask and remove the unit before killing, then wait a full reload cycle before believing the result.
Alongside it: XMRig dropped as /root/supervisor; a Komari agent, an open-source monitoring tool repurposed as a RAT; TrafficMonetizer proxyware in Docker; shell wrappers replacing top and ps that filter the real output through grep -v for nine of their own artefact names, byte-identical fleet-wide; six two-byte decoys that do nothing; a watchdog killing interactive crontab -e sessions. Anti-forensics: chattr deleted so we could not clear immutable flags, +ia on /tmp and /etc/cron.d, artefacts timestomped to 2016-08-25.
2 September, evening: into the guests
At 18:48:16 109.123.233.205 began a serialised sweep through the guest inventory — one to two seconds per VM, exhausting one node before moving on. Every POST .../agent/exec was paired with a GET .../agent/exec-status, so the output of each command was read back. Payloads landed 19:03–19:18; a second pass from 21:00 installed cron persistence.
The vector is qemu-guest-agent, confirmed in the API logs rather than inferred. A host with root can execute commands inside any guest running the agent, as root, with the output returned — no guest credential, no network path into the guest, no guest-side vulnerability required. Guest configurations were read as well.
The negative control holds alongside the logs: every affected guest had the agent installed, and guests without it were untouched. Nothing else separated the two sets — not distribution, not kernel, not exposed services, not which node they ran on. We are not publishing counts or identifiers; affected customers were contacted individually.
Inside the affected guests: XMRig under an innocuous name, a watchdog, a hidden binary hooked from root's crontab every minute, the same top and ps wrappers found on the hosts, the same decoys, and persistence in service-account crontabs and /etc/rc.local.
3–5 September: the restore that didn't take
We began rebuilding hosts from backup.
This is where our backup design mattered, and it is the part we would most want another operator to copy. Backups are pulled, not pushed: the backup system reaches in and takes a copy, and the host being backed up initiates nothing, holds no credential for the destination, and cannot write to or delete anything there. Destinations are cross-zone — three zones, each host copied into the other two, in different buildings. A total compromise of one zone leaves two intact copies of it somewhere that zone has no path to. That is the only reason a clean rsync source existed at all.
The first attempt did not take.
We ran the restore in place, against a live host: rsync -aHAX --delete from the backup over the existing filesystem. It completed without error. The rootkit was still resident.
The reason is one syscall. The rootkit hides its files from getdents, and rsync --delete enumerates the destination with getdents. Files it cannot see are files it does not delete, so the unit file, the binaries and the loader all survived a restore that reported complete success.
We had known since the previous day that it hid from ls and find. Both of those use getdents as well. We understood the mechanism and still did not extrapolate it to the tool we were about to rebuild 22 hosts with, and the gap was only closed when a restored node failed its own check.
So the procedure changed, and this is the sequence that worked for most of the cluster:
- Disable, mask and remove the rootkit, and confirm with
bpftool map showthat it is actually gone. - Reboot, so that nothing of it remains loaded in the kernel.
rsync -aHAX --deletefrom backup — now against a host that is telling the truth about its own filesystem.- Reboot again, and rsync a second time to confirm the result is stable.
A minority of hosts would not boot at all and had to be rebuilt from outside: a Debian live USB, activate the volume group, e2fsck, mount root and ESP, clear immutable flags, rsync from backup, then grub-install and update-grub in a chroot. Always re-run grub-install — rsync replaces /boot/grub modules without updating core.img, and you will boot to a rescue prompt.
Worth noting that the live-USB path never had the hiding problem. With the compromised kernel not running, nothing is hooked, getdents tells the truth and --delete removes everything it should. If you can restore a suspect host from outside it rather than from within it, do — it is slower, and it removes an entire class of doubt.
The general form is worth stating, because it is not specific to this rootkit: any tool that decides what to remove by listing a directory can be lied to by a kernel that has been taught to lie, and that is most of them.
A related trap: chattr +i on /etc/cron.d files blocks rsync --delete outright, and chattr itself had been deleted, so clearing the flags needs a direct FS_IOC_SETFLAGS ioctl.
What a per-node restore cannot clean
/etc/pve must be excluded from the restore. It is a live FUSE mount backed by pmxcfs, replicated across every node; restoring it per-node is not meaningfully possible, because surviving nodes overwrite whatever you write or you break quorum trying. Preserving it is what keeps 22 nodes in sync.
And /etc/pve is exactly where the persistence lived — the rogue accounts, their ACLs, and the API tokens.
So we rebuilt 22 hypervisors from clean backups and every one came back into a cluster configuration that still held the planted credentials. The hosts were clean; the cluster was not. In any clustered system with a shared, replicated control plane, per-node restore does not touch the shared plane. The shared configuration needs its own audit, and it has to finish before you restore service.
7 September: recovery announced, the cluster re-entered, and the re-entry caught
We returned Zone C to service and published a customer notice. That evening, between 20:31 and 23:10, credentials that had survived the rebuild were used against the cluster from three addresses — 150.230.217.250 and 146.56.115.18 (both Oracle Cloud) from 20:31, and 113.201.200.228 (China Unicom Shaanxi) from 21:55. At 22:22 came a query for the backup schedule, followed immediately by the list of guests with no backup configured. That is target selection for destruction. The backups themselves were unreachable, which is why the question had to be asked at all.
We had missed the credentials. During the rebuild we rotated passwords, SSH keys and the contents of /etc/pve/priv/, and we did not enumerate API tokens or reconcile the account list against our own provisioning design. Both had been planted on 2 September, and both came back with the cluster configuration for the structural reason described above.
What we had built in the meantime is what stopped it. In the days after the intrusion we rebuilt our authentication monitoring on the assumption that the hypervisor's own authentication logic cannot be trusted — that a valid Proxmox session is not evidence of a legitimate one. Every authentication is now scrutinised across several vectors and against its own history to judge whether it is actually what it claims to be, regardless of how correctly Proxmox itself authenticated it.
That system was live by the evening of 7 September, and it worked. At 21:24 a fresh root@pam token was minted and walked across ten source addresses. The first use succeeded at 21:31. The ban landed, and every attempt from the remaining nine addresses was refused. One request of ten got through before the door closed on a credential that was, as far as Proxmox was concerned, entirely valid — which is the whole point of not trusting the platform's own verdict.
Where it did not hold is the more useful lesson. The account planted on 2 September had been named to match our own customer-account convention, and our checks at that point judged privilege by what an identity was called rather than by what it could actually do — so it authenticated cleanly and was not challenged. That definition has since been corrected to effective permission.
8–9 September: the full audit
A cluster-wide log review found more than we expected.
Forty-two API tokens on root@pam, of which 41 were presented — and every one of those 41 authenticated successfully. Across five days, not a single token request was refused; the pool simply stopped on its own at 16:59 on 7 September, hours before the one minted that evening ran into our new checks. All 42 first appear on 2 September and none earlier; thirty-nine were used from a single host, 192.236.217.18. Eighteen show exactly two successful requests each inside a two-minute window and were never used again — created, verified, parked. Twenty-one were polled on a timer, fifteen of them recording exactly 221 requests, roughly one poll per token every 75 minutes, running through our entire rebuild and stopping the day we announced recovery. What they reached says what they were for: liveness checks and per-VM configuration reads across all 22 nodes, keeping an inventory current.
We had rotated passwords, SSH keys and the contents of /etc/pve/priv/. We had not enumerated tokens, and that was our omission. They do not appear in pveum user list, so an account audit does not surface them; the authoritative list is /etc/pve/priv/token.cfg, in cleartext, readable by anyone with host root.
And ten rogue accounts, all named to blend into our conventions — two used, eight never touched at all. None of the ten exists in our provisioning design, and the silence of the eight is the point rather than a qualification on it: a dormant account is what a planted backdoor looks like before anyone uses it.
Nobody here was establishing a way back in; they were establishing a dozen, using a handful, and leaving the rest in reserve. Had we killed the token used on 7 September and stopped there, eight accounts would still be sitting in the cluster config. Which is the argument for auditing by enumeration rather than investigation: don't look for the backdoor that was used — list every account, token and ACL in the shared configuration and reconcile each against your own design. Anything you cannot explain is theirs.
What we changed
Management-plane access restricted, on the assumption that the Proxmox authentication logic is not foolproof — as with any other hypervisor management interface — with mechanisms in place to detect and block such access. Every token deleted and every rogue account removed. A cluster-configuration diff is now mandatory before service is restored. Log retention extended and access logs forwarded off-cluster — everything in this report came from one file that was one rotation from being gone. A periodic bpftool map show check added to host monitoring.
No customer storage was destroyed and no personal data was involved: our sales platform and customer database sit on separate infrastructure outside the zone, never reachable from it. We contacted affected customers individually and rebuilt guests alongside them.
Summary
Assume the management API will have authentication bypasses. This one was fixed in 2023 and disclosed in 2026, and the first probes reached us within half an hour of the advisory. Our response was to stop treating a successful Proxmox login as evidence of a legitimate one: we now run our own authentication monitoring in front of it, on the assumption that the platform's verdict on who you are may simply be wrong. That is what caught the re-entry on 7 September. Every hypervisor platform in production use has had a pre-authentication flaw in its management plane, and all of them will have another. That is a software problem rather than a Proxmox problem, and changing vendor buys a different disclosure calendar, not immunity.
Proxmox is good software and we intend to keep running it. It is also why we had an architecture worth writing about: three independent clusters with cross-zone backup is a lot of infrastructure for a company our size, and Proxmox made it affordable enough that we built it rather than planning to. Much of this investigation was possible because the platform is transparent — ordinary Debian underneath, plain-text configuration, an API that logs what it does.
The backup design is what saved us, and it is cheap to copy. Pull, don't push, so no host holds a credential that can destroy its own recovery path, and put the copies in another zone in another building. If the platform can authenticate to, write to or delete its own backups, you have replication rather than backup — and replication faithfully copies a wiped volume to every replica. Assume too that there is more than one way back in, and more than one party: ours left 42 tokens and 10 accounts and used a fraction of each, so cleanup that chases the observed intrusion path will miss what was staged and never touched.
And watch for the things you never do. The whole attack was recorded in pveproxy/access.log while we were reading process lists on individual hosts. If you know which tokens should exist, which systems should call guest-exec, and that nobody should open a root console on a hypervisor from the internet, then anything else is an alarm with no false positives and no tuning required.
We published this because the findings are more useful in the open than in our own runbook, and because we would have liked to read it on 1 September.
Indicators of compromise
# Vulnerability
CVE-2023-54391 / PSA-2026-00043-1, libpve-access-control < 8.0.4, TCP 8006
# Addresses that obtained HOST ROOT (POST /nodes/<n>/termproxy -> 200), 1-2 Sep
# 33 external addresses inside the 1-2 Sep window (a 34th on 3 Sep); six reached all 22 nodes. Behaviour and payloads
# differ enough that these are not one party. Six fleet-wide, by shell count:
# 77.239.124.107 1957 shells / 22 nodes 2 Sep 00:12-23:59
# 103.252.119.62 1891 shells / 22 nodes 2 Sep 16:00-17:00
# 34.87.182.62 138 shells / 22 nodes 2 Sep 20:29-22:29
# 104.28.210.248 86 shells / 22 nodes 2 Sep 17:27-19:47
# 113.169.91.82 34 shells / 21 nodes 2 Sep 17:19-21:58
# 49.212.184.129 22 shells / 22 nodes 2 Sep 18:54-18:55 (61 seconds)
# Also: 186.241.77.199 (86/13), 185.220.101.141 Tor (36/1), 69.63.208.23 (16/8),
# 192.42.116.21 Tor (12/5), 123.253.140.5 (10/5), 103.91.64.211 (7/4),
# 38.54.45.205 (6/5), 103.213.4.16 (5/5), 135.136.55.117 (5/5), and a tail of ~20 more.
# Addresses with documented roles beyond console access
77.239.124.107 AS198364 BanatSync RO Largest single presence. 2 Sep 00:12 - 8 Sep 23:02,
6,855 reqs, 1,957 root shells across all 22 nodes.
223.118.51.66 AS58453 China Mobile Intl HK
FIRST ROOT SHELL. 1 Sep 22:43 ticket, 22:58
POST /nodes/xb1/termproxy = 200. 74 min before the
dpkg window on that node. 2,058 reqs, xb1 only.
192.236.217.18 AS36352 ColoCrossing 2 Sep 00:21 - 7 Sep 16:59, 6,418 reqs. Token pool:
39 root tokens, 75-minute rotation.
109.123.233.205 AS141995 Contabo Asia 2 Sep 18:26-21:51, 2,235 reqs. Guest compromise:
guest-exec sweep into agent-enabled guests,
output read back via exec-status.
103.213.4.16 AS7720 Skywolf Tech 2 Sep 22:01 - 7 Sep 20:37, 12,616 reqs. Sustained
dashboard use; node termproxy REFUSED (403).
150.230.217.250 AS31898 Oracle Cloud 2 Sep 21:44 - 7 Sep 22:52, 7,122 reqs. Present during
the original intrusion AND the 7 Sep return; VM consoles,
container build.
113.201.200.228 AS4837 China Unicom Shaanxi (CNCGROUP-SN)
7 Sep 21:55-23:10, 3,281 reqs. Firewall allow-list edit,
ban flushing.
146.56.115.18 AS31898 Oracle Cloud 7 Sep 20:48-20:59, 506 reqs.
# Other probing around the intrusion window
64.120.92.41 AS59253 Leaseweb SG 1 Sep 16:42-17:17, 47 reqs — first anomalous request
154.201.90.135 AS402506 Zenixcloud 1 Sep 17:42-20:04, 1,257 reqs
171.218.213.62 AS4134 CHINANET 1 Sep 17:57-20:05
146.19.216.125 AS134677 Dromatics SG 1 Sep 18:00 - 2 Sep 02:28, 631 reqs
104.168.59.54 AS36352 ColoCrossing 1 Sep 21:54 - 2 Sep 02:28
192.3.252.195 AS36352 ColoCrossing 1 Sep 21:55 - 2 Sep 02:40
31.56.48.186 AS56971 CGI Global HK 1 Sep 21:16 - 2 Sep 00:14, 821 reqs (ends 2 min AFTER
the intrusion began)
103.69.128.237 AS63473 HostHatch 1 Sep 22:00, 64 reqs
186.241.77.199 AS400619 ArossCloud 2 Sep 01:21-03:39
185.213.155.235 AS39351 31173 Services 2 Sep 09:14 - 3 Sep 04:55
123.253.140.5 AS131151 Imcloud TW 2 Sep 09:40-10:24
23.234.96.171 AS11878 tzulo 2 Sep 09:25-09:42
61.219.247.88 AS3462 HiNet TW 2 Sep 14:56-15:34
82.140.187.31 AS209854 Cyberzone PA 2 Sep 15:49-17:06
61.185.179.116 AS4134 CHINANET 2 Sep 16:08-16:22
103.91.64.211 AS55720 Gigabit MY 2 Sep 16:09-17:58
38.54.45.205 AS154177 Light Node HK 2 Sep 16:58-17:58
216.238.92.210 AS20473 Vultr 2 Sep 17:33-19:48
193.56.116.128 AS62240 Clouvider 2 Sep 20:04-20:13
34.87.182.62 AS396982 Google Cloud 2 Sep 20:29-22:29
151.246.186.157 AS215355 Alice Networks 3 Sep 00:54-01:12
164.92.212.184 AS14061 DigitalOcean 3 Sep 02:30-04:05, 11,812 reqs
# 7 Sep return, 21:24-21:50 — one stolen root token walked across ten addresses, every one
# banned on first use. Nine are external and listed here; the tenth was one of our own nodes
# appearing as the client on an inter-node forwarded request.
111.18.134.143 AS9808 China Mobile
111.19.97.183 AS9808 China Mobile
111.19.36.63 AS9808 China Mobile
111.19.6.147 AS9808 China Mobile
163.142.254.126 AS17816 China Unicom Guangdong
49.232.193.106 AS45090 Tencent Cloud
106.75.135.53 AS58466 CHINANET Guangdong IDC
106.75.143.31 AS58466 CHINANET Guangdong IDC
156.232.96.234 AS135377 UCloud HK
# Failed scanning — high volume, zero successful authentications
155.117.232.145 AS399486 Virtuo CA 1 Sep, ~6,000 reqs in one hour enumerating
/nodes/*/certs/info — all 501, wrong method
AS14061 DigitalOcean, ~990 guest-exec attempts 2 Sep 07:00-09:00, ALL 401:
159.89.133.180 134.199.147.61 68.183.177.1 207.154.222.214
137.184.98.30 64.227.126.240 178.62.124.12 68.183.68.63
138.197.150.101 206.189.199.39 46.101.3.110 168.144.165.219
164.92.198.3
AS16509 Amazon (single burst, 2 Sep 21:41:09):
18.236.16.236 32.185.38.45 32.186.186.201 35.160.23.151
44.253.128.155 54.68.103.104 52.43.49.134 44.253.44.103
# Pre-advisory window, 26-31 Aug: NO targeted activity found.
# First host root shell: 1 Sep 22:58, ~6.5 hours after the advisory.
# Only own infrastructure and untargeted internet background noise.
# Mining pools / C2
hello.itsmybirthdaytoday.pw:53535
43.224.34.224:16004 AS20473 Vultr, JP customer range
102.220.160.178:53535 AS51396 AfriNIC, no registered abuse contact
tz.5671234.xyz Komari C2
# Komari enrolment key — grep fleet-wide
e0edE4xUxqFWlCp8HsZfaWfK
# Monero wallet (substitution target)
858VteJWn4Bdkp33nWFgNFcuBWD54q1idj
# Delivery
gitlab.com/Kanedias/xmrig-static (legitimate repo abused as CDN)
# Binaries / decoys
supervisor javab cpanel-lite idle .rguard ptsty .khp .proxy_ start_javab
kdevtmpfsi cd1 wd1
ip6network irqbalanced kswaped pamdicks rctlcli systemd-network (2-byte decoys)
# Wrapper scripts
/usr/bin/top 153 bytes sha256 6b30708c9e7d0ee724edca74680d29f2bc048d1faf06f072f609740ac8dd795c
/bin/ps 148 bytes originals preserved as *.original
# eBPF map names
protected_pids exempt_comm hidden_names hidden_filename ghost_files ghost_fds
pending_proc_op proc_fds pending_getdent pending_gdents pending_opens
pending_reads pending_stats map_fds map_buff_addrs map_name_addrs
map_prog_array map_to_replace_ replace_.rodata execve_r.rodata rb_regex
# eBPF program names
protect_kill protect_tkill protect_tgkill ap_ptrace ap_vm_readv ap_vm_writev
handle_getdents handle_openat_e handle_close handle_close_ex handle_read_ent
handle_read_exi handle_newfstat handle_fstat_en handle_fstat_ex hf_enter
hf_exit handle_execve find_possible_addrs check_possible_addresses
overwrite_addresses
# BPF program tag, constant across hosts
942e07341aaeae7a (find_possible_addrs)
# systemd unit signature
Description=System Management Daemon
ExecStart=/usr/lib/.<16 hex> attach
PIDFile=/run/.<16 hex>
# Timestomp value
2016-08-25 00:00:00
# Log signatures worth alerting on
identity field containing "!" (API token use)
POST .*/agent/(exec|file-write) → 200 (guest-exec success)
POST .*/nodes/[^/]+/termproxy → 200 (root shell on a hypervisor)
A caution on all of the above. These are addresses that appeared in our logs doing the things described. They are not an attribution. Most of this ran through rented or borrowed infrastructure, and much of it was very likely hosted on machines whose owners were themselves compromised and have no idea they were involved — a hijacked VPS is the ordinary way this work gets done. Treat every entry as a host that participated, not a party responsible. Cloud and hosting addresses also rotate between customers, and the four China Mobile addresses are dynamic carrier space, so an entry meaningful this month may belong to an unrelated tenant in six.
Tor exit relays used for the 2 September enumeration are not listed — they appear on every public exit list and say nothing about who was behind them. If you are blocking from this list, block freely; if you are contacting an operator on it, contact their abuse desk rather than their customer. All of it has gone to the relevant abuse contacts and national CERTs.
Netfront has operated hosting and network infrastructure for over two decades. hosting.netfront.net — corrections, or if you have seen the same eBPF component elsewhere.
Friday, September 11, 2026
