
Laboratório de demonstração seguro por padrão mostrando como o endurecimento de contêineres (imagens distroless, não-root, sistema de arquivos somente leitura, segredos injetados em tempo de execução) pode neutralizar um RCE crítico em Next.js/React Server Actions (CVE-2025-55182 “React2Shell”), com implantações lado a lado seguras vs inseguras e logs de exploit
Este projeto demonstra uma vulnerabilidade crítica de Execução Remota de Código (RCE) em uma aplicação Next.js (especificamente via Server Actions) e como o endurecimento da infraestrutura neutraliza efetivamente o ataque mesmo quando a vulnerabilidade no código permanece.
Ele contrasta uma implantação padrão "Insegura" (Unsafe) com uma implantação "Segura" (Safe) endurecida usando imagens Distroless e Sistemas de Arquivos Somente Leitura.
Vulnerabilidades de software são inevitáveis. Quando o código falha, sua infraestrutura deve impedir que o atacante expanda seu ponto de apoio.
Uma RCE Crítica (CVE-2025-55182, também conhecida como React2Shell) existe na implementação dos Componentes de Servidor React (RSC) usada pelo Next.js.
spawnSync) sem autenticação.curl, wget, ls, cat) para roubar segredos ou baixar malware.
child_process.spawnSync() do Node.js. Isso executa binários diretamente, sem precisar de um shell (/bin/sh).chmod +x) e o executa.Os logs a seguir demonstram como as tentativas de ataque parecem da perspectiva da aplicação. Esse contraste destaca vividamente a eficácia das medidas de segurança.
logs/server.safe.log)Os logs mostram falhas repetidas (ENOENT).
spawnSync tenta executar ls, id, curl. A imagem Distroless simplesmente não possui esses binários. Não é apenas a ausência de um shell; as próprias ferramentas desapareceram.[Instrumentation] Logging initialized. Writing to: /app/logs/server.safe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"2. Verify Binary was Written","verification":{...},"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"","stderr":"","error_obj":{"message":"spawnSync /tmp/hello_test EACCES","code":"EACCES"},"success":true}`'
logs/server.unsafe.log)Os logs confirmam a execução bem-sucedida de comandos e a manipulação do sistema de arquivos.
[Instrumentation] Logging initialized. Writing to: /app/logs/server.unsafe.log
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"1. Write Initial Chunk to /tmp/hello_test","success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"step":"4. Execute Binary","stdout":"Hello from Go binary!\\n","stderr":"","error_obj":null,"success":true}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"id","args":[],"stdout":"uid=0(root) gid=0(root) ...","stderr":"","status":0,"signal":null}`'
⨯ Error: NEXT_REDIRECT
... digest: '`{"command":"cat","args":["/app/.env"],"stdout":"","stderr":"cat: can\'t open \'/app/.env\': No such file or directory\n","status":1,"signal":null}`'
(Nota: Nos logs inseguros, cat /app/.env falha acima porque o arquivo está nomeado como .env na raiz, mas ls -la nos logs completos revelaria a estrutura do diretório.)
Tentativa de executar comandos shell padrão.
id, ls, cat .env e acessar dados sensíveis.spawnSync /bin/sh ENOENT. Não há shell para executar comandos.Tentativa de contornar a "falta de ferramentas" enviando um binário personalizado.
/tmp/malware.chmod +x.EROFS: read-only file system.Pode um atacante carregar um binário em uma variável e executá-lo diretamente da memória?
global.payload = "..."), e então executá-lo.child_process do Node.js (spawn, exec) exigem um caminho de arquivo. Elas não podem executar um buffer ou string diretamente.memfd_create (uma chamada de sistema para criar um arquivo anônimo na RAM).memfd_create nativamente. Acessá-lo exigiria um addon C++ (como ffi-napi) pré-instalado em node_modules.gcc, make), um atacante não pode construir esse addon rapidamente.Imagens "Distroless" contêm apenas sua aplicação e suas dependências de tempo de execução. Elas não contêm gerenciadores de pacotes, shells ou ferramentas UNIX padrão.
ls), baixar arquivos (curl) ou escalar privilégios facilmente.Configure seu runtime de contêiner para montar o sistema de arquivos raiz como somente leitura.
docker-compose.yml:
read_only: true
tmpfs:
- /tmp:noexec # CRÍTICO: bloqueie explicitamente a execução!
/tmp (escrita bem-sucedida), mas a execução falha com EACCES (Permissão Negada) por causa da flag noexec. Isso equilibra funcionalidade (tmp gravável) com segurança.Não envie arquivos .env em suas imagens de contêiner. Se um atacante puder ler arquivos (ex: cat .env), seus segredos estão comprometidos.
environment do Docker).Inicie o Ambiente:
Ambas as aplicações segura e insegura estão definidas em um único arquivo docker-compose.yml.
docker compose up --build -d
Execute os Exploits: Você pode executar os exploits contra as portas específicas para ver a diferença.
Mirando o App Inseguro (Porta 3001):
# 1. Standard RCE (LotL) - SUCCEEDS
python exploit/poc.py http://localhost:3001
# 2. Advanced Attack (BYOL) - SUCCEEDS
python exploit/poc_advanced.py http://localhost:3001
Mirando o App Seguro (Porta 3000):
# 1. Standard RCE (LotL) - FAILS (ENOENT)
python exploit/poc.py http://localhost:3000
# 2. Advanced Attack (BYOL) - FAILS (EACCES/EROFS)
python exploit/poc_advanced.py http://localhost:3000
Limpeza:
docker compose down
| Característica | ❌ Ambiente Inseguro (Porta 3001) | ✅ Ambiente Seguro (Porta 3000) |
|---|
| Imagem Base | node:20-alpine (Contém ls, curl, wget, etc.) | gcr.io/distroless/nodejs20-debian12 (Sem shell, sem ferramentas) |
| Sistema de Arquivos | Gravável (Padrão Docker) | Somente Leitura (read_only: true) |
| Segredos | Arquivo .env no disco (Vulnerável a cat .env) | Variáveis de Ambiente (Injetadas em tempo de execução) |
| Usuário | root (Padrão) | Não-root (Imposto pelo Distroless) |