Skip to the content.

← 回到說明文件首頁 · English | 繁體中文

安全性評估:裝了 jt-snmpd 之後,這台 Windows 多了什麼風險

量測項目 內容
量測日期 2026-08-24
版本 jt-snmpd 1.0.0
受測主機 Dell Latitude E5270 / Windows 10 22H2
量測端 LibreNMS 26.8.1

量測範圍說明。 以下數字是對 1.0.0 量的,那個版本只提供 SNMPv2c。 SNMPv3 是 1.1.0 才加入的;唯一可能被它改變的那個數字已於 2026-08-27 重新量過,寫在第 1 節。其餘各節不受影響,因為 v3 增加的是第二種認證方式, 不是第二批可以讀的資料。

本文件中的位址一律使用 RFC 5737 的文件用保留範圍(192.0.2.0/24)。 量測是在真實內網上做的,數字本身未經修改。

監控代理程式最不能接受的失敗,是它自己成為被攻擊的入口。 這份文件把「多開了什麼、能被利用到什麼程度、擋住的是哪一層」逐項量測並記錄, 數字都可以自行複製。


1. 新增的網路暴露面:UDP/161

裝了之後多開一個 UDP 監聽埠。SNMP 是反射式 DDoS 的經典協定, 攻擊者偽造來源 IP 送一個小請求,讓被害者收到大回應。

實測放大倍率

16:51:53.019679 192.0.2.10.58590 > 192.0.2.63.161: UDP, length 39
16:51:53.037594 192.0.2.63.161 > 192.0.2.10.58590: UDP, length 774

39 → 774 位元組 = 19.8×,而且這是攻擊者刻意把 GETBULK 的 max-repetitions 拉到 1000 的結果。

沒有上限的 agent 在同樣請求下會回傳到訊息大小上限為止;以本專案 6,000 筆 OID 的規模推算,回應會膨脹到數萬位元組,放大倍率進入 1000× 等級。 壓住它的是兩個機制:

機制 效果
MAXREP_CAP 25 無論請求要多少,最多處理 25 次重複
回應位元組上限 1400 不分片,超過即截斷回應

放大倍率的理論上限因此是 1400/39 ≈ 36×,實測 19.8×。

SNMPv3 不會把這個上限拉高。 RFC 3414 要求代理服務在對方出示任何憑證之前 就得回應一次 engine 探索,所以有一次未認證的交換是任何設定都拿不掉的。 2026-08-27 實測:

20:58:25.394461 192.0.2.10.55736 > 192.0.2.63.161: UDP, length 64
20:58:25.396923 192.0.2.63.161 > 192.0.2.10.55736: UDP, length 125

64 → 125 位元組 = 1.95 倍,對照上面 v2c 的 19.8 倍。回應是一個 report PDU, 內容是 engine ID 與兩個計數器,大小不隨發送端要求什麼而變 —— 沒有 max-repetitions 可以灌。要找反射放大的攻擊者沒有理由捨 v2c 而就它; 而一個開了 v3_only 的站台,等於把一個 19.8 倍的反射器換成 1.95 倍的。

反射攻擊實際上打不出去

放大倍率只有在「能讓回應送到被害者」時才有意義。要做到這件事, 攻擊者必須偽造一個在管理網段內的來源位址,而這被兩層擋住:

jt-snmpd (UDP 161): Allow proto=UDP port=161 from=192.168.1.0/255.255.255.0
jt-snmpd (ICMPv4):  Allow proto=ICMPv4         from=192.168.1.0/255.255.255.0

第二層的存在理由是第一層可能被設寬(客戶自行改防火牆), 以及深度防禦:BER 解碼器是攻擊面最大的一塊,能避免未授權封包碰到它最好。


2. 程式碼執行風險:服務以 LocalSystem 執行

這是最需要誠實面對的一項。解析器的漏洞就是 SYSTEM 權限的遠端執行。

為什麼一定要 LocalSystem

磁碟 SMART 需要 SMART_RCV_DRIVE_DATA 這類 IOCTL,而 \\.\PhysicalDriveN 必須以 GENERIC_READ | GENERIC_WRITE 開啟, 那需要系統管理權限。虛擬服務帳戶做不到。

緩解:權杖權限已縮減到最小

SERVICE_NAME: jt-snmpd
        PRIVILEGES : SeChangeNotifyPrivilege
                   : SeSystemProfilePrivilege
                   : SeIncreaseQuotaPrivilege

LocalSystem 預設帶約 30 項權限,這裡只留 3 項。已移除的包括 SeDebugPrivilege(讀寫任意程序記憶體)、SeTcbPrivilegeSeImpersonatePrivilege(權杖竊取,提權慣用路徑)、 SeLoadDriverPrivilege(載入核心驅動)、SeBackupPrivilege / SeRestorePrivilege (繞過 ACL 讀寫任意檔案)。

即使解析器被攻破,攻擊者拿到的是一個無法偵錯其他程序、無法模擬其他使用者、 無法載入驅動的 SYSTEM 內容。

緩解:唯讀,且沒有 oracle

$ snmpset -v2c -c <community> 192.0.2.63 .1.3.6.1.2.1.1.6.0 s "PWNED"
Timeout: No Response from 192.0.2.63
$ snmpget -v2c -c <community> -Oqv 192.0.2.63 .1.3.6.1.2.1.1.6.0
"LAB"

SET 不是「回錯誤」而是直接丟棄,不回應就不提供任何可探測的訊息。 實作上是不覆寫 write_variables(),所以唯讀不是靠檢查,是靠沒有那條路徑

緩解:不引入核心驅動

CPU 溫度需要讀 MSR,那需要核心驅動。業界慣用的 WinRing0 已列入 Microsoft 易受攻擊驅動封鎖清單,為了一個溫度值,在數百台政府與醫院主機上 安裝一個能任意讀寫 MSR 與實體記憶體的驅動,是把監控工具變成提權管道。 本專案因此不提供 CPU 核心溫度。這是專案的硬性規則之一:不為了任何一個數值 引入核心驅動。

緩解:解析器面對敵意輸入

所有「長度 / 偏移量取自緩衝區自身」的解析都當成敵意輸入處理, 且與採集分離成純函式,用惡意位元組做 property test (tests/test_sensors_parsing.py,34 項)。

ctypes 是 Python 記憶體安全性失效之處:緩衝區配小了,核心會直接寫出界。 已修正的案例,CallNtPowerInformation 原型用 os.cpu_count() 決定緩衝區大小, 在超過 64 個邏輯處理器的機器上會少報,導致核心寫出界。 現改用 GetActiveProcessorCount(ALL_PROCESSOR_GROUPS)


3. 自我阻斷服務

一個亂寫的長度欄位不會讓 Python 越界,但會讓迴圈跑四十億次, 在「不能拖慢 host」的硬性要求下,這是一次自我 DoS。所有解析都有上限常數 (MAX_INSTANCESMAX_WMI_BUFFERMAX_PROCESSORSMAX_NAME_CHARS)。

輪詢造成的負載已在實機上量測:7,000 倍真實輪詢速率下, 固定基準工作負載退化 0.41%(程序設為 BELOW_NORMAL_PRIORITY_CLASS 之後)。 單次完整 walk 12.5 ms CPU;LibreNMS 每 5 分鐘一次 → 實際約 0.004% CPU。

每來源 token bucket 限制請求速率,prune() 定期清除過期項目, 避免來源表本身成為記憶體耗盡的途徑。


4. 資訊揭露:刻意不提供的資料

威脅模型認定主要對手是已經在內網的攻擊者。 一次未認證的唯讀 walk 若能取得完整弱點評估報告與內網拓撲, 這個 agent 就是攻擊者的資產。因此以下預設關閉。 在受測機器上實際 walk,這些是內建服務答得出來、而本 agent 不答的 3,999 個 OID:

Subtree Windows 內建 SNMP jt-snmpd 不提供的理由
hrSWInstalled 660 0 每個軟體的精確版本 = 現成的 CVE 清單
hrSWRun / hrSWRunPerf 1,863 0 哪套 EDR 在跑、裝在哪 = 客製化規避
tcpConnTable 1,230 0 完整連線清單
udpTable 50 0 服務清單
ipNetToMedia(ARP) 448 0 內網 ARP 表 = 橫向移動的目標清單

介面篩選也順帶減少了揭露:只輸出實體網路卡,VPN 虛擬卡、WFP 篩選器驅動程式、 通道介面都不出現。


5. 尚未緩解的風險(誠實列出)

風險 現況 計畫
community 明文傳輸 v2c 沒有加密或認證,網路上可嗅探 SNMPv3 已實作(SHA-256 + AES-128、authPriv,localized key 以 DPAPI machine scope 儲存)。要完全排除 v2c 就設 v3_only
來源 IP 可偽造 UDP 無連線,白名單擋得住反射但擋不住盲送 v3 的認證可根治;目前靠速率限制與唯讀降低影響
執行檔未簽章 目前沒有 Authenticode 憑證 日後規劃透過開源專案憑證方案申請。在那之前完整性以公布的 SHA-256 建立,手動信任、WDAC 雜湊規則與自行簽章見程式碼簽章
pysnmp BER 解碼器 前置閘門擋在它之前,但授權來源仍會走到它 專用小解析器(Phase 1),縮小這塊攻擊面
LocalSystem 權限已縮減為 3 項 無法再降:SMART IOCTL 需要系統管理權限

6. 對照:不裝 jt-snmpd 的替代方案

方案 UDP/161 執行身分 寫入 來源控制 速率限制 支援狀態
Windows 內建 SNMP LocalSystem(權限未縮減) 支援 SET PermittedManagers在解析之後生效 Microsoft 已標記為棄用
jt-snmpd LocalSystem(僅 3 項權限) 唯讀 前置閘門,在 BER 解碼器之前 每來源 token bucket 持續維護
不做 SNMP 監控 不開 沒有監控

取代內建 SNMP 是攻擊面的淨減少:少了 SET、少了 27 項權限、 少了約四千個資訊揭露 OID,多了速率限制與前置來源檢查。


7. 如何自行複製這些量測

# 放大倍率
tcpdump -i any -n -q "udp port 161 and host <目標>" &
snmpbulkget -v2c -c <community> -Cr1000 -Cn0 <目標> .1.3.6.1.2.1

# 唯讀驗證
snmpset -v2c -c <community> <目標> .1.3.6.1.2.1.1.6.0 s "TEST"   # 應逾時
snmpget -v2c -c <community> -Oqv <目標> .1.3.6.1.2.1.1.6.0        # 應不變
# 權杖權限
sc qprivs jt-snmpd
# 防火牆範圍
Get-NetFirewallRule -DisplayName 'jt-snmpd*' | Get-NetFirewallAddressFilter

相關文件