
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
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.
O handler de upload do FUXA chama condicionalmente fs.mkdirSync(parent, { recursive: true }) antes de fs.writeFileSync, o que cria uma ambiguidade de EACCES: um /home/<usuário>/ inexistente falha com EACCES na tentativa de mkdir (o processo não pode criar diretórios em /home/ de propriedade do root), enquanto um /home/<outro>/ existente com modo 0700 falha com EACCES na própria escrita. Mesmo errno, significados diferentes. O POC elimina a ambiguidade analisando o token de syscall do err.message encaminhado pelo servidor (formato libuv "<CODE>: <reason>, <syscall> '<path>'") e só relata EACCES-na-abertura (ou qualquer syscall que não seja mkdir) como "outro usuário existe." Isso elimina a lista falsa positiva de movimento lateral que uma sonda ingênua baseada apenas em errno produziria.
Se nodeRedEnabled = true nas configurações vazadas, os endpoints administrativos do Node-RED embutido do FUXA (/nodered/flows/deploy, etc.) são acessíveis sem autenticação por causa de uma verificação de whitelist no cabeçalho Referer (node-red/index.js:134-136) que aceita qualquer requisição cujo Referer contenha /editor, /viewer ou /lab. Isso gera RCE não autenticada instantânea por meio de um nó de função sem exigir reinicialização. O modo recon sinaliza essa condição; os operadores devem preferi-lo quando disponível.
settings.js preservam a configuração do alvoAo gerar uma substituição do settings.js (modos settings-rce e webshell), o POC primeiro obtém a configuração ativa via /api/settings e a serializa em JSON como o corpo de module.exports (JSON é um subconjunto válido de literal de objeto JavaScript). Isso preserva o uiPort, allowedOrigins, secureEnabled e outras configurações de runtime do alvo, para que o aplicativo continue a funcionar normalmente após a substituição. O POC avisa explicitamente quando o alvo tem secureEnabled = true ou um bloco smtp, pois a redação no lado do servidor remove secretCode (o fallback de JWT será ativado) e smtp.password (o e-mail será interrompido até ser restaurado manualmente).
POST /api/upload de fontes não autenticadas, particularmente com valores de destination contendo .. ou caminhos absolutos do filesystem na respostaGET /api/settings de fontes não autenticadas (normalmente usado apenas pela UI autenticada)/tmp/healthcheck*, /tmp/.fuxa-probe-* ou /home/*/.fuxa-probe-* (nomes padrão de marcadores de canário e de sonda — o POC permite sobrescrevê-los para evitar IOCs óbvios, portanto a ausência não descarta exploração)settings.js, /etc/cron.d/* ou ~/.ssh/authorized_keys que não correspondem a uma ação de administrador/api/upload é protegido pela cadeia de middleware padrão na versão corrigida./api/upload e /api/settings./root/, sem escrita em /etc/cron.d/, sem persistência no host via cron).nodeRedEnabled = false) se ele não estiver em uso ativo.1.2.10 do FUXAserver/api/projects/index.js:193 no FUXA 1.2.9Pesquisa, PoC e relatório por Anthony Cihan (Hann1bl3L3ct3r), Líder de Segurança Ofensiva.
Este repositório contém código de exploit funcional para uma vulnerabilidade crítica em um produto ICS/SCADA. Ele é publicado sob princípios de divulgação responsável, após a correção pelo fornecedor, em benefício de defensores (autores de detecção, respondedores a incidentes) e testadores de segurança autorizados.
Use apenas contra sistemas que você possui ou para os quais tenha autorização explícita e por escrito para testar. O uso não autorizado deste código contra sistemas de terceiros é ilegal na maioria das jurisdições e será tratado como tal pelo autor. O autor não aceita responsabilidade por uso indevido.
Se você é operador do FUXA e deseja ajuda para validar seu nível de correção contra este PoC em condições controladas, entre em contato com o autor.
Publicado para fins autorizados de teste de segurança e pesquisa defensiva. Consulte LICENSE para os termos completos.
| 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 |