The silent page awaits the waking dawn.
xmrig.service — XMRig 挖矿木马,每 10 秒重启一次
tianji-reporter.service — 可疑的"监控上报"服务
tianji-telemetry.service — 每 60 秒向 localhost:12345 上报的 Python 脚本
systemctl stop/disable xmrig tianji-reporter tianji-telemetry
rm -f /etc/systemd/system/xmrig.service \
/etc/systemd/system/tianji-reporter.service \
/etc/systemd/system/tianji-telemetry.service \
/usr/lib/systemd/system/tianji-reporter.service
rm -rf /123
rm -f /usr/local/bin/tianji-report.py /etc/tianji-reporter.json
systemctl daemon-reload && systemctl reset-failed
Background
This server's specs were nothing to write home about:
The symptoms that night were textbook: SSH logins were agonizingly slow, everything lagged, and eventually the load average spiked to 353 (a catastrophic number for a 2-core box), iowait hit 31%, swap was completely full, and the machine was basically catatonic. The only option was a forced reboot from the Tencent Cloud console.
Step 1: Pinpointing the cause of the crash
Once I finally managed to squeeze in over SSH, I started with sar (historical data recorded by sysstat) to trace the load curve during the crash window, then used journalctl and dmesg to check the OOM logs, and the truth surfaced fast:
Conclusion: a 3.6GB machine running 20 containers with no memory limits was guaranteed to hit OOM — it was only a matter of time.
But the real “surprise” was still waiting further down the line.
Step 2: An unexpected discovery — a crypto-mining trojan
While working through the system state, I found a few things in the systemd unit list that had no business being there:
xmrig.service had already logged 108,000 restarts — meaning it had been failing and retrying every 10 seconds for 12 days straight. Since the binary had long since been deleted (later confirmed: Tencent Cloud's YunJing quarantined it shortly after installation), systemd could do nothing but spin in place forever.
Reconstructing the attack chain
Drawing on /var/log/auth.log and journalctl records, the full attack chain looks like this:
Afterward I went to confirm the vulnerability — MCSManager had already published an advisory: every version up to and including v10.16.2 was affected, and my panel was running v10.16.2, the very last version before the patch.
Earlier traces
SSH was in bad shape too: root password login was exposed to the public internet, drawing 12,000+ brute-force attempts a week. During the investigation I personally watched two unfamiliar IPs probing repeatedly at the preauth stage.
Step 3: Cleanup
Once I'd confirmed the intrusion, I did the cleanup in three rounds:
1. Close the entry point
Ports 23333/23334 were cleared, with no leftover processes.
2. Stop all containers
Ran docker stop on all 20 containers. This also relieved the memory pressure as a side effect — swap usage dropped to zero, freeing up safe headroom for the work ahead.
3. Remove malicious persistence
One small hiccup: tianji-reporter.service had a second copy in /usr/lib/systemd/system/ that the first cleanup pass missed; systemctl list-unit-files turned it up again, and only after deleting that copy was everything truly clean. The xmrig binary in YunJing's quarantine was preserved at /usr/local/qcloud/YunJing/quara/ as forensic evidence.
Step 4: A thorough backdoor sweep
With the known backdoors gone, I still had to make sure the attacker hadn't left any other “gifts” behind. I went through it item by item:
What I checked
Result
Accounts / sudo
✅ Only root/ubuntu/lighthouse, no stray uid 0 accounts; passwd/shadow/sudoers untouched since June
cron
✅ Only the Tencent Cloud agent and my own scripts
systemd units
✅ No custom units beyond the 3 already removed
authorized_keys
✅ Only coolify's key and my own; no attacker public key
SUID files
✅ Nothing unusual (the ones inside container snapshots are normal)
deleted-but-running binaries
✅ None
/etc/ld.so.preload
✅ Empty
/etc/hosts, LD_PRELOAD, profile.d, rc.local
✅ Clean
Executables in /tmp /var/tmp /dev/shm
✅ None
dpkg integrity
✅ Only a sudoers md5 change (my own NOPASSWD setup)
Docker images
✅ No mining images
Recent files in /root
✅ All from my own 1Panel install and ops scripts
For the batch of binaries changed in system directories between July 28 and early September (systemd, curl, openssl, dockerd, etc.), I cross-checked the apt history — all were normal unattended-upgrades updates. Conclusion: apart from the three known spots, I found no other backdoors.
Step 5: Hardening and rebuilding
Once the sweep was done, I did three things:
fail2ban
It took effect the moment it was installed — 16 brute-force failures logged within 10 minutes. This machine gets attacked every single day.
Swap 4G → 8G
I did this with all containers stopped and the memory pressure relieved, so it was perfectly safe.
Restore containers + memory limits
Ran docker start on all containers. Along the way I hit one snag: the clickhouse container failed to start, reporting no such volume — the crash had wiped out Docker's volume metadata, but the data directory under /var/lib/docker/volumes/ was still there.docker volume create After recreating a volume with the same name, the data was intact and the container started up normally.
Finally, I gave the OOM culprit chronoframe a memory cap:
Note that docker update may get overwritten when Coolify redeploys, so you need to set it in the app config as well.
Post-mortem summary
Afterword
I never imagined MCSManager would turn out to have a vulnerability this big.
What surprised me even more is how little information about this vulnerability is out there! I only found it by searching keywords after GLM-5.3-Flash mentioned MCSManager's compromise path.
The official handling was even more absurd — they just posted a Release: https://github.com/MCSManager/MCSManager/releases/tag/v10.17.0, didn't file it under Security advisories, assigned no CVE, and didn't wait long enough before disclosing the flaw (though I doubt many people are in the habit of updating anyway). Either way, that approach is a bit off. Like I said: just set up your server by hand from the command line (
Root causes of this incident
Lessons I learned
Still to do (next steps)
A 2-core, 3.6GB machine crammed with self-hosted services, plus an exposed panel and wide-open SSH — that combination was bound to blow up sooner or later. I hope this post-mortem helps readers avoid a few of these pitfalls.
(The IPs, mining pool addresses, and other details in this article are included only to reconstruct the incident. Please do not use them for any illegal purpose.)