2 個 bug 串成 MikroTrick!
簡介
1. 背景
路由器作業系統位於內部網路與網際網路之間;一旦遭到入侵,流量、Credential 及所有下游區段皆會暴露。2026 年 9 月 5 日,CERT Polska 揭露了六個 RouterOS 漏洞 ,其中兩個在公開修復方案發布前已遭實際利用 [2] 。Bishop Fox 在受控實驗室中重現了完整的接管過程,隨後識別出帶有與該攻擊活動一致 Artifact 的現場裝置 [1] 。在多項進入向量(entry vector)中,此報告聚焦於前置條件最少的路徑:CVE-2026-67279,無需 Credential 或任何現有使用者帳號知識即可觸發。
2. 漏洞鏈
MikroTrick 結合了不同 信任邊界 的兩個失敗點。第一個 (CVE-2026-67279) 讓未經認證的連線能夠存取僅應在登入後才存在的功能;第二個 (CVE-2026-86060) 使登入程序將攻擊者提供的資料視為受信任的管理員身份。單獨來看,每個缺陷的影響有限;但結合起來,它們能產生完整的未經認證管理員 Session。
2.1 第一階段:Rekeying 跳過認證
SSH 通常強制執行嚴格的順序:加密、認證,然後允許 Session channel。RouterOS 在一般連線上遵循此順序,但當未經認證的用戶端發起 rekeying (標準協定操作)時,缺陷便會出現。在受影響的版本上,完成 rekey 會將連線轉換至認證後階段,即使沒有使用者通過認證;用戶端隨後可以開啟 Session channel、請求終端機並送出執行請求。該 Session 不帶任何身份,但它跨越了隔離用戶端與 RouterOS 登入程序的邊界,從而啟用第二階段。CERT Polska 報告指出此路徑還可操作 RouterOS 管理命名空間中的檔案;Bishop Fox 確認了此繞過行為,但未實際執行檔案操作 [1] [2] 。
2.2 第二階段:使用者名稱變為受信任指令
RouterOS 在獨立的登入輔助程式中建立 Session 身份,該程式以用戶端提供的使用者名稱作為命令列引數被呼叫。該輔助程式保留了一項針對受信任本機呼叫者的舊版功能:以
破折號為前綴的引數(Dash-prefixed argument)
並非使用者名稱,而是已開啟 file descriptor 的選擇器(selector),輔助程式會從中讀取受信任的身份記錄(使用者名稱加上權限遮罩)。SSH 服務將遠端使用者名稱轉發至此介面時,並未過濾特殊語法。因此,提供使用者名稱
-2
即指示輔助程式從 file descriptor 2 讀取其身份,而在受影響的 RouterOS 7.x 版本上,該描述符(descriptor)連接到用戶端自身的終端機。攻擊者因而撰寫了輔助程式認為來自受信任本機程序的記錄;一個名為 administrator 且具備完整權限集的記錄便在沒有任何正常認證步驟的情況下被安裝
[1]
。
3. 攻擊序列
4. 修補程式分析
這兩項修復分別落在不同的元件中並解決不同的條件,證實攻擊鏈包含兩個獨立的漏洞,而非單一缺陷的兩種症狀 [1] 。
4.1 修復一:分派前要求 Session Policy
RouterOS 7.23.4 在連線協定分派器(Dispatcher)中新增了遺漏的授權檢查。使用正規化變數名稱並保留反編譯器控制流的情況下,受影響的 7.23.3 與已修復的 7.23.4 之間的主要差異如下 [1] :
- // 7.23.3 dispatcher: 0x08057404 —— virtual address of the vulnerable function in the extracted ELF
- // 7.23.4 dispatcher: 0x08057482 —— address of the same patched function in the fixed ELF
- connection = *(session + 0xac); // dereference the session object at offset 0xac to reach its embedded connection struct
- if (*(uint8_t *)(connection + 0x78) != 0) { // existing guard: proceed only if the connection-state flag at +0x78 indicates an active channel
- // Added in 7.23.4: —— the entire block below is new; it did not exist in 7.23.3
- if (*(uint32_t *)(connection + 0x108) == 0) // NEW CHECK: read the session policy mask at +0x108; zero means authentication never assigned permissions
- return 2; // NEW: abort with status 2; the packet is never dispatched, killing the unauthenticated path
- dispatch_connection_packet(packet_type); // unchanged: reached only when a non-zero policy proves the session was authenticated
- }
該欄位保存 session policy 遮罩:已認證的 session 具有非零 policy;rekey 路徑的 session 則沒有。這項新測試恢復了狀態轉換未能強制執行的授權不變性。RouterOS 6.49.21 對 6.x 佈局中位於
connection + 0xd8
的 policy 欄位套用了相同的檢查——偏移量不同,但安全決策相同。當檢查觸發時,裝置會返回狀態 2 且不附帶描述性 SSH 錯誤,在 Paramiko 中顯示為
Negotiation failed
——這是 Bishop Fox 偵測器的修補端特徵
[1]
。
4.2 修復二:在 SSH 邊界拒絕 Helper 控制語法
第二項修復在建構登入輔助程式的命令列之前驗證使用者名稱。已修補的 SSH 二進位檔從 libumsg.so 匯入
validLoginParamInput
;7.23.4 中位於
0x08062192
附近的呼叫點簡化如下
[1]
:
- if (!validLoginParamInput(username)) // sanitize FIRST: run the new validator on the raw client-supplied username string
- return invalid_user_input; // reject immediately: the username never reaches helper construction, so "-2" cannot be formed
- invoke_login_helper(username); // only validated usernames proceed to helper invocation with argv construction
驗證器會拒絕空使用者名稱、破折號為前導、前導或尾隨空格以及控制字元 [1] :
- bool validLoginParamInput(string_view username) // signature: operates on a length-delimited view, no copying
- {
- if (username.length == 0) // rule 1: empty usernames carry no identity and are always invalid
- return false; // reject at the earliest possible point
- if (username.data[0] == '-' || username.data[0] == ' ') // rule 2: the LEADING-DASH check is security-critical — it blocks
- return false; // the helper's fd-selector syntax ("-2"); leading space also
- // prevents whitespace-based smuggling
- if (username.data[username.length - 1] == ' ') // rule 3: trailing space can hide the true username during logging/display
- return false; // so the ambiguity is rejected outright
- for (size_t i = 0; i < username.length; i++) { // rule 4: scan EVERY byte of the username
- unsigned char c = username.data[i]; // read as unsigned to avoid signed-char comparison pitfalls
- if (c <= 0x1f || c == 0x7f) // reject C0 control bytes (0x00–0x1F) and DEL (0x7F), which could
- return false; // inject framing or confuse downstream parsers
- }
- return true; // all rules passed: safe to pass to the login helper
- }
值得注意的是,舊版 file-descriptor 模式保持不變:修復保留了該功能以供受信任的本機呼叫者使用,同時精確地在不受信任的網路輸入進入受信任本機介面的位置進行驗證。受影響的輔助程式會去除破折號前綴,套用
atoi
,並將結果用作描述符;在 7.x 上,它接受描述符 0–32,讀取最多
0x1000
位元組,並將前兩個 NUL 分隔欄位解析為使用者名稱和 policy 遮罩,而 6.x 缺乏描述符上限檢查
[1]
。
5. 觀察到的持久化 Artifact
在授權測試期間,Bishop Fox 發現某些裝置的揮發性登入歷史不再涵蓋疑似入侵的時間窗口——RouterOS 預設將日誌駐留在記憶體中——而持久性 Configuration 保留了證據:一個腳本
logrotate
和一個排程器
daily-maint
,每天重新執行該腳本,若帳號被移除則重建完整權限帳號
[1]
:
/system script print detail where owner="0" // list scripts owned by numeric identity 0 — the anomalous owner used as a hunting lead 0 name="logrotate" owner="0" // entry 0: persistence script; its name mimics a benign log-maintenance utility source=<redacted: recreates a full-privilege account if absent> // body (redacted): if the rogue account is deleted, // execution silently re-creates it — self-healing persistence /system scheduler print detail where owner="0" // list scheduled tasks with the same anomalous numeric owner 0 name="daily-maint" start-time=03:00:00 interval=1d on-event=logrotate owner="0" // entry 0: fires daily at 03:00 (low-observation hours) every 1 day, // triggering logrotate; interval=1d keeps persistence alive indefinitely
未經授權的帳號對每個裝置使用不同的密碼,這與自動化的逐裝置 Credential 生成一致。關鍵在於,所有持久化物件將其擁有者呈現為數字身份 0,而非具名管理員。在 RouterOS 7.23.3 上的實驗室確認顯示,透過漏洞鏈建立的排程器返回
owner="0"
,而透過正常管理員登入建立的排程器則返回
owner="admin"
[1]
:
# created through the chain name="poc-test" ... owner="0" // object created by the takeover chain has NO resolved owner name; // RouterOS falls back to raw UID 0 — the behavioral fingerprint of the chain # created through a normal admin login name="normal-test" ... owner="admin" // object created via a genuine authenticated session resolves to the // literal account name "admin", proving the rendering difference is reliable
數字擁有權是一條獵捕線索,而非證據——合法服務建立的物件也可能具有此特徵。防禦人員應將
owner="0"
物件與意外帳號、腳本、排程器、Configuration 歷史、遠端日誌、標記狀態以及已知良好基準進行關聯分析
[1]
。
6. 非侵入式偵測
Bishop Fox 發布了一個針對 CVE-2026-67279 的安全偵測器,不發送任何使用者名稱、認證嘗試、命令或子系統請求。它將控制連線(key exchange,然後 channel 請求)與測試連線(在相同 channel 請求之前額外進行一次 rekey)進行比較。受影響的版本會拒絕控制連線,但在 rekeying 後會開啟 channel [1] :
[VULNERABLE] router.example.net:22 // verdict line: host and port of the assessed device control (no rekey): refused [Unable to open channel.] // control connection: channel request denied as expected for an // unauthenticated session — the correct-behavior baseline test (rekey) : opened [channel opened without userauth (SSH-2.0-ROSSSH)] // test connection: the extra rekey advanced the state machine, so the // SAME request was accepted — only this differential yields "VULNERABLE"
逾時、演算法不匹配和網路干擾會被報告為無結論而非已修補,以避免偽陰性。該特徵已針對 6.49.20 和 7.23.3(受影響)以及 6.49.21 和 7.23.4(已修復)進行了驗證 [1] 。
7. 與先前 RouterOS 研究的比較
MikroTrick 與早期的 RouterOS 缺陷有顯著不同。CVE-2023-30799 是透過 Winbox 或 HTTP 介面從 admin 提升至 super-admin 的已認證權限提升漏洞,並利用 ROP 鏈武器化以獲取 root shell [3] 。該漏洞假設已有有效 Credential 並提升現有 Session;MikroTrick 不需要任何 Credential,並從一個精心製作的使用者名稱字串製造出管理員身份。相反地,CVE-2023-30799 能在底層系統上達成任意程式碼執行,而重現的 MikroTrick 則獲得完整的 RouterOS 管理控制權。兩者都指向同一個防禦結論:持久性 Configuration 物件的存在時間往往長於揭示入侵所需的揮發性日誌,因此入侵評估必須檢查 Configuration,而不僅僅是日誌歷史 [1] [3] 。
8. 緩解措施
受影響的部署應升級至 RouterOS 6.49.21、7.23.4、7.24.2 或更新版本,這些版本解決了公告中的所有六個漏洞 [1] [2] 。修補程式可防止新的攻擊,但無法驅逐已建立的攻擊者;受影響的路由器應被視為潛在已遭入侵,需審查特權使用者、腳本、排程器、代理伺服器、tunnel、Configuration 歷史、遠端日誌及標記狀態。若懷疑遭到入侵,應在任何重置之前收集 Configuration 和日誌,從經過驗證的基準重新設定,並輪換路由器可能觀察到的每個密碼、金鑰或 secret。
結論
更廣泛地說,MikroTrick exemplifies 一個反覆出現的設計風險:當上游元件失去對認證狀態的追蹤時,原為受信任本機呼叫者設計的機制就會變成遠端攻擊面。穩健的設計會在不受信任的輸入進入受信任介面的邊界處進行驗證——這正是
validLoginParamInput
所實現的功能——並將協定狀態機視為絕不隱式推進的安全邊界。修補狀態與入侵狀態是兩個獨立的問題:安裝的版本決定了暴露程度;持久性 Configuration 物件則記錄了該暴露是否曾被利用。